From aa749c5b373489c4bcb5b5924954d21e5dea7ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Wed, 27 May 2026 21:57:29 +0800 Subject: [PATCH] fix: stop stuck desktop provider streams (#634) Desktop sessions already had CLI stream-idle recovery code available, but child CLI processes did not enable it by default. A provider stream that stopped producing chunks without closing could therefore leave the desktop UI showing a running turn indefinitely. Enable the existing watchdog for desktop-launched CLI sessions and lock the launch environment with a focused regression test. Constraint: Desktop provider streams must surface a terminal state when upstream TCP stalls silently. Rejected: Add a frontend-only running-state timeout | it would mask the hung CLI stream without recovering the turn. Confidence: high Scope-risk: narrow Directive: Keep desktop stream-stall handling at the CLI stream boundary unless the transport contract changes. Tested: bun test src/server/__tests__/conversation-service.test.ts Tested: bun run check:server --- src/server/__tests__/conversation-service.test.ts | 10 ++++++++++ src/server/services/conversationService.ts | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/server/__tests__/conversation-service.test.ts b/src/server/__tests__/conversation-service.test.ts index 87ba2ce7..aa438e22 100644 --- a/src/server/__tests__/conversation-service.test.ts +++ b/src/server/__tests__/conversation-service.test.ts @@ -549,6 +549,16 @@ describe('ConversationService', () => { expect(env.CC_HAHA_DESKTOP_AWAIT_MCP_TIMEOUT_MS).toBe('5000') }) + test('buildChildEnv enables stream idle watchdog for desktop CLI sessions', async () => { + const service = new ConversationService() as any + const env = (await service.buildChildEnv( + '/tmp', + 'ws://127.0.0.1:3456/sdk/test-session?token=test-token', + )) as Record + + expect(env.CLAUDE_ENABLE_STREAM_WATCHDOG).toBe('1') + }) + test('buildSessionCliArgs forwards the selected runtime model and effort to the CLI process', () => { const service = new ConversationService() as any const args = service.buildSessionCliArgs( diff --git a/src/server/services/conversationService.ts b/src/server/services/conversationService.ts index 7ce4b501..1bc74586 100644 --- a/src/server/services/conversationService.ts +++ b/src/server/services/conversationService.ts @@ -983,6 +983,8 @@ export class ConversationService { ...cleanEnv, CLAUDE_CODE_ENABLE_TASKS: '1', CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING: '1', + // Desktop must fail stuck provider streams instead of leaving the UI running forever. + CLAUDE_ENABLE_STREAM_WATCHDOG: cleanEnv.CLAUDE_ENABLE_STREAM_WATCHDOG || '1', CLAUDE_CODE_DIAGNOSTICS_FILE: cliDiagnosticsPath, CLAUDE_COWORK_MEMORY_PATH_OVERRIDE: this.resolveDesktopAutoMemoryPath(workDir), CALLER_DIR: workDir,