Skip to content

claude-cli one-shot output path diverged from live session: is_error delivered as success, raw NDJSON fallback, unbounded line buffer, POSIX UTF-8 chunk corruption #98896

Description

@obviyus

What happened

The claude-cli one-shot output path has diverged from the live-session path; four related defects, all in the same classification seam:

  1. is_error results delivered as successful replies. parseClaudeCliJsonlResult (src/agents/cli-output.ts:408-432) and parseCliJson (:378-400) never check is_error/error subtypes; only the live session does (src/agents/cli-runner/claude-live-session.ts:938). The repo's own fixture (src/agents/test-helpers/claude-api-error-fixture.ts:34-40) shows Claude emits {"type":"result","subtype":"success","is_error":true,"result":"API Error: 400 ..."} — on the one-shot path that raw API error string is delivered to the user as the assistant reply, the session binding is persisted, and no failover happens.
  2. Raw NDJSON transcript delivered as the reply. When no result envelope is recognized, parseCliOutput falls back to { text: params.raw.trim() } (cli-output.ts:1078-1084) — up to 1 MiB (one-shot) / 8 MB (live) of raw stream-json sent to the user. The streamed-assistant-text fallback in getOutput() (:962-971) exists only for the Gemini dialect.
  3. Unbounded line buffer. createCliJsonlStreamingParser grows lineBuffer without bound (cli-output.ts:948-955) and execute.ts:1123-1133 keeps pushing even after stdoutParseExceeded. The live path caps pending lines at 8 MB and aborts (claude-live-session.ts:955-967); the one-shot path can allocate GBs from a babbling child before the overall timeout.
  4. UTF-8 corruption at chunk boundaries on POSIX. src/process/supervisor/adapters/child.ts:175-196 decodes each stdout Buffer independently; the incremental decoder in src/infra/windows-encoding.ts:158-172 is only constructed on win32. A multibyte character split across a 64 KB pipe read decodes to U+FFFD on both sides, inside a valid JSON string, so corrupted text flows silently to the user.

Expected behavior

One classification seam in cli-output.ts shared by live and one-shot paths: is_error/error subtypes classify to errorText, stream-json dialects never fall back to raw transcript text, line buffering is capped like the live path, and stdout decoding is incremental on all platforms.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.bugSomething isn't workingclawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.impact:crash-loopCrash, hang, restart loop, or process-level availability failure.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.no-staleExclude from stale automation

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions