feat(claude-code): use stream-json protocol for persistent sessions#7029
feat(claude-code): use stream-json protocol for persistent sessions#7029codefromthecrypt merged 2 commits intomainfrom
Conversation
Use the same stream-json protocol as the official Claude Agent SDKs instead of spawning a new `claude -p` process per turn. This eliminates OS argument size limits (E2BIG) on long conversations, preserves session context across turns, and adds proper error event handling. Signed-off-by: Adrian Cole <adrian@tetrate.io>
There was a problem hiding this comment.
Pull request overview
This PR refactors the Claude Code provider to use a persistent subprocess with the stream-json protocol instead of spawning a new process per turn with the -p flag. This change addresses OS argument size limits (E2BIG errors) on long conversations and enables persistent multi-turn sessions with proper state management.
Changes:
- Switched from one-shot
-p <messages_json>invocations to persistent--input-format stream-json --output-format stream-jsonprotocol - Added
CliProcessstruct to manage persistent stdin/stdout/stderr of the Claude CLI subprocess - Converted message formatting from Anthropic-style structured tool blocks to text-based representations (tools are rendered as
[tool_use: ...]and[tool_result: ...]) - Implemented incremental message sending (only new messages are sent per turn)
- Added comprehensive unit tests for message conversion, NDJSON protocol, response parsing, and error handling
| pub const CLAUDE_CODE_DOC_URL: &str = "https://code.claude.com/docs/en/setup"; | ||
|
|
||
| #[derive(Debug)] | ||
| struct CliProcess { |
There was a problem hiding this comment.
is this part in theory useful for other cli providers?
…webtoken-10.3.0 * origin/main: (54 commits) Switch tetrate tool filtering back to supports_computer_use (#7024) feat(ui): add inline rename for chat sessions in sidebar (#6995) fix: handle toolnames without underscores (#7015) feat(claude-code): use stream-json protocol for persistent sessions (#7029) test(providers): add model listing to live provider suite (#7038) Agent added too much (#7036) fix(deps): bump tree-sitter to 0.26 and set sqlx default-features=false to fix RUSTSEC advisories (#7031) feat: add image support and improve error resilience for Codex (#7033) fix(providers): Azure OpenAI model listing 404 during configure (#7034) fix(deps): bump bat to 0.26.1 to resolve RUSTSEC-2026-0008 (#7021) Don't swallow Tetrate errors (#6998) docs: remove hardcoded_stuff links (#7016) fix(ui): keep Hub chat input from overlapping SessionInsights on paste (#6719) Clean up css (#6944) docs: aws bedrock bearer token auth (#6990) docs: extended custom provider headers support (#7012) feat(cli): add type-to-search filtering to select/multiselect dialogs (#6862) feat(ci): add cargo-audit workflow for scanning rust vulnerabilities (#6351) feat: add User-Agent header to MCP HTTP requests (#6988) chore(deps-dev): bump webpack from 5.102.1 to 5.105.0 in /ui/desktop (#6996) ... # Conflicts: # Cargo.lock
* origin/main: Remove build-dependencies section from Cargo.toml (#6946) add /rp-why skill blog post (#6997) fix: fix snake_case function names in code_execution instructions (#7035) Document max_turns settings for recipes and subagents (#7044) feat: update Groq declarative data with Preview Models (#7023) fix(codex): propagate extended PATH to codex subprocess (#6874) Switch tetrate tool filtering back to supports_computer_use (#7024) feat(ui): add inline rename for chat sessions in sidebar (#6995) fix: handle toolnames without underscores (#7015) feat(claude-code): use stream-json protocol for persistent sessions (#7029) test(providers): add model listing to live provider suite (#7038) Agent added too much (#7036) fix(deps): bump tree-sitter to 0.26 and set sqlx default-features=false to fix RUSTSEC advisories (#7031) feat: add image support and improve error resilience for Codex (#7033) fix(providers): Azure OpenAI model listing 404 during configure (#7034) fix(deps): bump bat to 0.26.1 to resolve RUSTSEC-2026-0008 (#7021) Don't swallow Tetrate errors (#6998) docs: remove hardcoded_stuff links (#7016) # Conflicts: # ui/desktop/src/components/GooseSidebar/AppSidebar.tsx
Replace OnceCell with Mutex<Option<CliProcess>> so a dead CLI process can be detected and respawned. The persistent stream-json process (introduced in block#7029) could die unexpectedly (e.g. after Ctrl+C), leaving the OnceCell permanently poisoned with a stale process whose stdin pipe was closed. Now execute_command checks process liveness via try_wait before each turn and, if a write still hits a broken pipe (race between check and write), respawns once and retries transparently. Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: rabi <ramishra@redhat.com>
Replace OnceCell with Mutex<Option<CliProcess>> so a dead CLI process can be detected and respawned. The persistent stream-json process (introduced in block#7029) could die unexpectedly (e.g. after Ctrl+C), leaving the OnceCell permanently poisoned with a stale process whose stdin pipe is closed. Signed-off-by: rabi <ramishra@redhat.com>
I think this breaks the existing functionality of using Ctrl+C to send a new message, as Ctrl+C (SIGINT) kills the persistent child process and, since it's stored in a OnceCell, it can never be respawned — all subsequent writes fail with a broken pipe. I've proposed #7070 fix and refactor it a bit, so that I can rebase my streaming PR. |
…lock#7029) Signed-off-by: Adrian Cole <adrian@tetrate.io>
Summary
Switch the Claude Code provider from one-shot
-pinvocations to the stream-json protocol (--input-format stream-json --output-format stream-json). This is the same protocol used by the official Claude Agent SDKs (Go, TypeScript, Elixir) to communicate with the CLI as a persistent subprocess.The old approach spawned a new
claude -p <messages_json>process per turn, passing the entire conversation as a CLI argument. This hit OS argument size limits (E2BIG) on long conversations and lost all session context between turns. The new approach keeps a single CLI process alive, sends messages as NDJSON on stdin, and reads responses line-by-line from stdout -- giving goose persistent multi-turn sessions, image support, and proper error handling for free.Type of Change
AI Assistance
Testing
Related Issues
Relates to #6972