Skip to content

feat(claude-code): use stream-json protocol for persistent sessions#7029

Merged
codefromthecrypt merged 2 commits intomainfrom
claude-refactor
Feb 6, 2026
Merged

feat(claude-code): use stream-json protocol for persistent sessions#7029
codefromthecrypt merged 2 commits intomainfrom
claude-refactor

Conversation

@codefromthecrypt
Copy link
Collaborator

@codefromthecrypt codefromthecrypt commented Feb 6, 2026

Summary

Switch the Claude Code provider from one-shot -p invocations 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

  • Refactor / Code quality
  • Bug fix

AI Assistance

  • This PR was created or reviewed with AI assistance

Testing

GOOSE_PROVIDER=claude-code GOOSE_MODEL=sonnet target/release/goose run --text "say just hello"
# starting session | provider: claude-code model: sonnet
#     session id: 20260206_26
#     working directory: /Users/codefromthecrypt/oss/goose-goosed
# Hello! 👋

Related Issues

Relates to #6972

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>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-json protocol
  • Added CliProcess struct 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

Signed-off-by: Adrian Cole <adrian@tetrate.io>
pub const CLAUDE_CODE_DOC_URL: &str = "https://code.claude.com/docs/en/setup";

#[derive(Debug)]
struct CliProcess {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this part in theory useful for other cli providers?

@alexhancock alexhancock self-requested a review February 6, 2026 14:45
@codefromthecrypt codefromthecrypt added this pull request to the merge queue Feb 6, 2026
Merged via the queue into main with commit 4cfcd9b Feb 6, 2026
18 checks passed
@codefromthecrypt codefromthecrypt deleted the claude-refactor branch February 6, 2026 14:52
katzdave added a commit that referenced this pull request Feb 6, 2026
…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
zanesq added a commit that referenced this pull request Feb 6, 2026
* 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
rabi added a commit to rabi/goose that referenced this pull request Feb 7, 2026
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>
rabi added a commit to rabi/goose that referenced this pull request Feb 7, 2026
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>
@rabi
Copy link
Contributor

rabi commented Feb 7, 2026

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.

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.

kuccello pushed a commit to kuccello/goose that referenced this pull request Feb 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants