mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
Desktop official OAuth sessions inject a token into a long-lived CLI process to avoid OS credential prompts. After Windows sleep/wake that env token can become stale, so the server now pushes a refreshed token before each resumed user turn and the CLI clears its OAuth cache when the env token changes. The retry path also disables keep-alive after ECONNRESET/EPIPE by default, matching the stale pooled-socket failure mode reported on #651. Constraint: Real Win11 sleep/wake cannot be physically reproduced in this macOS worktree Rejected: Restart the CLI before every user turn | would add latency and discard useful process continuity Confidence: medium Scope-risk: moderate Directive: Keep official OAuth env-token updates paired with CLI-side OAuth cache invalidation Tested: bun test src/server/__tests__/conversation-service.test.ts --timeout 30000 Tested: bun test src/cli/__tests__/structuredIO.test.ts --timeout 30000 Tested: bun test src/services/api/withRetry.test.ts --timeout 30000 Tested: bun run check:server Tested: git diff --check Not-tested: Real Win11 sleep/wake with Claude official OAuth Not-tested: External ccswitch reqwest pool reset path when the reset is hidden behind the proxy Related: #658 Related: #651
50 lines
1.3 KiB
TypeScript
50 lines
1.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import type Anthropic from '@anthropic-ai/sdk'
|
|
import { APIConnectionError } from '@anthropic-ai/sdk'
|
|
import { _resetKeepAliveForTesting, getProxyFetchOptions } from '../../utils/proxy.js'
|
|
import { withRetry } from './withRetry.js'
|
|
|
|
describe('withRetry stale connections', () => {
|
|
test('disables keep-alive before retrying ECONNRESET connection failures', async () => {
|
|
_resetKeepAliveForTesting()
|
|
let attempts = 0
|
|
const cause = Object.assign(new Error('socket hang up'), {
|
|
code: 'ECONNRESET',
|
|
})
|
|
const staleConnection = new APIConnectionError({
|
|
message: 'Connection error.',
|
|
cause,
|
|
})
|
|
|
|
const generator = withRetry(
|
|
async () => ({} as Anthropic),
|
|
async () => {
|
|
attempts += 1
|
|
if (attempts === 1) {
|
|
throw staleConnection
|
|
}
|
|
return 'ok'
|
|
},
|
|
{
|
|
model: 'claude-opus-4-7',
|
|
thinkingConfig: { type: 'disabled' },
|
|
maxRetries: 1,
|
|
},
|
|
)
|
|
|
|
let finalValue: string | undefined
|
|
for (;;) {
|
|
const next = await generator.next()
|
|
if (next.done) {
|
|
finalValue = next.value
|
|
break
|
|
}
|
|
}
|
|
|
|
expect(finalValue).toBe('ok')
|
|
expect(attempts).toBe(2)
|
|
expect(getProxyFetchOptions().keepalive).toBe(false)
|
|
_resetKeepAliveForTesting()
|
|
})
|
|
})
|