Skip to content

fix(claude_code): parse message-form stream-json + raise on auth-failed (2.0.3)#205

Merged
CocoRoF merged 1 commit into
mainfrom
fix/claude-code-message-form-2.0.3
May 19, 2026
Merged

fix(claude_code): parse message-form stream-json + raise on auth-failed (2.0.3)#205
CocoRoF merged 1 commit into
mainfrom
fix/claude-code-message-form-2.0.3

Conversation

@CocoRoF
Copy link
Copy Markdown
Owner

@CocoRoF CocoRoF commented May 19, 2026

Summary

Two follow-up bugs from the user's prod run after 2.0.2 unblocked the streaming control flow:

1. output_len=0 despite the pipeline reporting SUCCESS

Claude Code 2.x's default stream-json output emits the full assistant message in one envelope:

{"type":"assistant","message":{"content":[{"type":"text","text":"hi there"}]}}

not the per-token delta shape ({"type":"assistant","delta":{"type":"text_delta",...}}) the parser was written for. The 2.0.2 accumulator returned an empty APIResponse for every session because its assistant.delta.text_delta branch never matched. User saw:

✓ s06_api
... (5 more stages succeed)
PIPELINE COMPLETE [SUCCESS]: 0 iterations
output_len=0

2. "Not logged in" placeholder came back as the assistant's reply

When the CLI's credential cache is empty it emits a normal-looking assistant frame:

{"type":"assistant","error":"authentication_failed","message":{"content":[{"type":"text","text":"Not logged in · Please run /login"}]}}

The parser accepted it as legitimate output. User got a "successful" turn whose only content was the auth-failure placeholder.

Fix

  • StreamJsonAccumulator — one shared parser used by both create_message_stream (streaming caller) and assemble_response_from_stream_json (non-streaming caller). Handles both shapes:
    • delta form (--include-partial-messages on, true streaming)
    • full-message form (Claude Code 2.x default)
    • Synthesises per-block text_delta / thinking_delta / tool_use events when the message form arrives so UI consumers see the same canonical shape they would with true streaming.
  • Auth failure: raise APIError(CLI_AUTH_FAILED) when the CLI tags a frame with error="authentication_failed". Host surfaces the auth problem to the user instead of running a successful pipeline that silently returned a placeholder.
  • stream_json_line_to_canonical_event (single-event translator) collapses a full message's text blocks into one text_delta for legacy consumers that don't use the accumulator.

Tests

  • test_create_message_stream_message_form_collects_text — message form yields text_deltas + populates terminal response
  • test_create_message_stream_authentication_failed_raises — auth_failed envelope raises APIError(CLI_AUTH_FAILED)
  • test_send_streaming_message_form_text — non-streaming caller picks up message-form text too
  • New ok_message_form / message_form_auth_failed scenarios in the fake binary mirror real CLI 2.1.144 output
  • Full tests/llm_client/187/187 pass

Release

2.0.3. Migration: none.

…n auth-failed (2.0.3)

Two follow-up bugs from the user's prod run after 2.0.2 unblocked the
streaming control flow:

1. ``output_len=0`` despite the pipeline reporting SUCCESS. Claude
   Code 2.x's default stream-json output emits the full assistant
   message in one envelope (``{"type":"assistant","message":{"content":
   [{"type":"text","text":"..."}]}}``) — not the per-token delta
   shape the parser was written for. The accumulator returned an
   empty APIResponse for every session because its
   ``assistant.delta.text_delta`` branch never matched.

2. ``"Not logged in · Please run /login"`` came back as the
   assistant's reply when the CLI's credential cache was empty.
   The CLI annotates that frame with ``error=authentication_failed``
   but otherwise looks like a normal assistant message; the parser
   accepted it as legitimate output.

Fix:

- Introduce ``StreamJsonAccumulator`` — one shared parser used by
  ``create_message_stream`` (streaming caller) and
  ``assemble_response_from_stream_json`` (non-streaming caller).
  Handles both stream-json shapes:
    * delta form (``--include-partial-messages`` on, true streaming)
    * full-message form (Claude Code 2.x default)
  Synthesises per-block ``text_delta`` / ``thinking_delta`` /
  ``tool_use`` events when the message form arrives so UI consumers
  see the same canonical shape they'd see with true streaming.
- Raise ``APIError(CLI_AUTH_FAILED)`` when the CLI tags a frame
  with ``error="authentication_failed"``. Host surfaces the auth
  problem to the user instead of running a successful pipeline that
  silently returned a placeholder.
- Update the single-event translator
  ``stream_json_line_to_canonical_event`` to collapse a full
  message's text blocks into one ``text_delta`` so legacy consumers
  that don't use the accumulator still see content.

### Tests

- ``test_create_message_stream_message_form_collects_text``
- ``test_create_message_stream_authentication_failed_raises``
- ``test_send_streaming_message_form_text``
- New ``ok_message_form`` / ``message_form_auth_failed`` scenarios
  in the fake binary mirror the real CLI 2.1.144 output.

Full ``tests/llm_client/`` — 187/187 pass.
@CocoRoF CocoRoF merged commit aaf2901 into main May 19, 2026
5 of 6 checks passed
@CocoRoF CocoRoF deleted the fix/claude-code-message-form-2.0.3 branch May 19, 2026 02:46
CocoRoF added a commit to CocoRoF/Geny that referenced this pull request May 19, 2026
…h_failed) (#812)

2.0.3 fixes two follow-on bugs the user hit on prod after 2.0.2:

1. Claude Code 2.x emits the full assistant message in one
   ``assistant.message.content[]`` envelope (not delta form) by
   default. The 2.0.2 accumulator only handled the delta form so
   sessions came back with ``output_len=0`` despite the pipeline
   reporting SUCCESS.
2. ``error="authentication_failed"`` envelopes were treated as normal
   assistant output. The "Not logged in · Please run /login"
   placeholder was returned as the agent's reply.

See CocoRoF/geny-executor#205 for the full root-cause + fix.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant