Skip to content

fix: read claude's JSON output as an event list too (ENG-9655) - #5

Open
fwereade wants to merge 1 commit into
mainfrom
fwereade/fix/claude-output-shape
Open

fwereade wants to merge 1 commit into
mainfrom
fwereade/fix/claude-output-shape

Conversation

@fwereade

@fwereade fwereade commented Nov 4, 2026 •

Copy link
Copy Markdown

what

_inference.envelope() now accepts both shapes of claude -p --output-format json: the result
object alone, or a list of the session's events, from which it takes the last type: "result"
event. A list with no result event (or any other non-object JSON) is still "no result envelope".

why

ENG-9655: with verbose on, claude -p --output-format json prints a JSON array of events (system/init, assistant,
rate_limit_event, result) instead of the bare result object. The ticket blamed Claude Code
2.1.318, but the cause is verbose: the default command loads user settings (--setting-sources user), so "verbose": true there is enough. envelope() discarded the list, so every
default-command model call raised
InferenceError, breaking assess.py ... show and distill.py ... show. The only workaround was a
custom STACKLET_GUARDRAILS_INFERENCE_CMD with text output, which marks the output as made by a
custom command and loses token and cost stats.

testing

  • tests/test_inference.py: the envelope tests (answer/tokens/cost, and the error reported on
    stdout with a non-zero exit) now run against both the object and event-list shapes. The
    no-envelope test also covers plain text, [], an event list with no result, and a bare JSON
    string.
  • just test (963 passed) and just lint pass.
  • Live on Claude Code 2.1.303, 2.1.310 and 2.1.317: the default command gives the object shape,
    and _inference.run returns the answer with tokens and cost.
  • Live on 2.1.310 with --verbose (and with --settings '{"verbose":true}'): the output is the
    event list. On main, run() raises "wrote no result envelope", as in the ticket. On this
    branch it returns OK with tokens and cost.

docs

None needed: no docs describe the envelope's shape. The new code comment says why it can be a list.

@fwereade
fwereade requested a review from a team as a code owner November 4, 2026 16:41
With verbose on, `claude -p --output-format json` prints a list of the
session's events (system/init, assistant, rate_limit_event, result)
rather than the result object alone. The default command loads user
settings, so `"verbose": true` there is enough. envelope() discarded the
list, so every default-command call raised InferenceError and assess.py
and distill.py failed. It now takes the last "result" event from a
list, and still reads the object form.

The envelope tests run against both shapes. Checked live on 2.1.310
with and without --verbose: main fails on the verbose output, this
reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fwereade
fwereade force-pushed the fwereade/fix/claude-output-shape branch from 8aa3f4d to 30fe9c0 Compare November 4, 2026 17:18

@ajkerrigan ajkerrigan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for the better explanation and fix 🍻

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.

2 participants