fix: handle Claude Code 2.x JSON array response in summarization#820
fix: handle Claude Code 2.x JSON array response in summarization#820areporeporepo wants to merge 2 commits intoentireio:mainfrom
Conversation
Claude Code 2.x changed `--output-format json` to return a JSON array of messages instead of a single object. The summarize package now tries the array format first (extracting the element with `type: "result"`), falling back to the single-object format for Claude Code 1.x compat. Fixes entireio#750 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates Entire CLI’s Claude CLI summarization parsing to handle the Claude Code 2.x JSON array-of-messages output format (while keeping Claude Code 1.x single-object backward compatibility), unblocking entire explain --generate and auto-summarization.
Changes:
- Extend
claudeCLIResponsewith atypefield and addparseClaudeCLIResult()to support both Claude Code 2.x (array) and 1.x (object) response shapes. - Update
ClaudeGenerator.Generate()to use the new parsing helper before unmarshaling the summary JSON. - Add unit tests covering array-format responses and the new parsing helper behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| cmd/entire/cli/summarize/claude.go | Adds array-aware parsing to extract the "type":"result" payload from Claude Code 2.x JSON output and keeps 1.x fallback. |
| cmd/entire/cli/summarize/claude_test.go | Adds tests for 2.x array responses and unit tests for parseClaudeCLIResult(). |
cmd/entire/cli/summarize/claude.go
Outdated
| // Claude Code 1.x returns a single JSON object; try that as a fallback. | ||
| resultJSON, err := parseClaudeCLIResult(stdout.Bytes()) | ||
| if err != nil { | ||
| return nil, err |
There was a problem hiding this comment.
Generate() returns parseClaudeCLIResult errors directly. For array/no-result (and empty-result) cases, parseClaudeCLIResult returns an unwrapped errors.New(...), so callers lose the "failed to parse claude CLI response" context (unlike the invalid-JSON path). Consider wrapping the returned error here (or consistently inside parseClaudeCLIResult) so all parse failures have a stable, grep-able prefix.
| return nil, err | |
| return nil, fmt.Errorf("failed to parse claude CLI response: %w", err) |
There was a problem hiding this comment.
Fixed — Generate() now wraps with fmt.Errorf for consistent grep-able prefix.
| for _, r := range responses { | ||
| if r.Type == "result" && r.Result != "" { | ||
| return r.Result, nil | ||
| } | ||
| } |
There was a problem hiding this comment.
In the 2.x array path, a {"type":"result","result":""} element currently triggers the generic "array contained no result element" error because the loop only returns when Result != "". Consider treating an explicit result element with an empty result as a distinct error (e.g., "contained empty result") to match the single-object behavior and make debugging easier.
| for _, r := range responses { | |
| if r.Type == "result" && r.Result != "" { | |
| return r.Result, nil | |
| } | |
| } | |
| hasEmptyResultElement := false | |
| for _, r := range responses { | |
| if r.Type == "result" { | |
| if r.Result == "" { | |
| hasEmptyResultElement = true | |
| continue | |
| } | |
| return r.Result, nil | |
| } | |
| } | |
| if hasEmptyResultElement { | |
| return "", errors.New("claude CLI response array contained empty result") | |
| } |
There was a problem hiding this comment.
Fixed — added hasEmptyResultElement tracking in the array path. Now returns 'contained empty result' (matching 1.x behavior) instead of the generic 'no result element' message.
| // claudeCLIResponse represents the JSON response from the Claude CLI. | ||
| // Claude Code 1.x returns a single object: {"result": "..."} | ||
| // Claude Code 2.x returns an array: [{type:"system",...}, {type:"result", result:"..."}] | ||
| type claudeCLIResponse struct { | ||
| Type string `json:"type"` | ||
| Result string `json:"result"` | ||
| } |
There was a problem hiding this comment.
The JSON example in this comment isn’t valid JSON ({type:"system"} is missing quotes around keys). Since this is explaining a wire format, it’s worth using valid JSON in the example to avoid confusion.
There was a problem hiding this comment.
Fixed — doc comment now uses valid JSON with quoted keys.
| name: "single object with empty result", | ||
| input: `{"result": ""}`, | ||
| expectError: "empty result", | ||
| }, |
There was a problem hiding this comment.
TestParseClaudeCLIResult covers empty result only for the 1.x single-object format. Since parseClaudeCLIResult now has distinct logic for the 2.x array format, consider adding a test case for an array that includes a { "type": "result", "result": "" } element to lock in the intended behavior/error message.
| }, | |
| }, | |
| { | |
| name: "array with empty result element", | |
| input: `[{"type":"system"},{"type":"result","result":""}]`, | |
| expectError: "empty result", | |
| }, |
There was a problem hiding this comment.
Added — new test case "array with empty result element" covers this path and expects the "empty result" error.
- Wrap parseClaudeCLIResult errors with consistent prefix in Generate() - Distinguish empty result element from missing result in array path - Fix invalid JSON in doc comment (unquoted keys) - Add test case for array with empty result element Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Claude Code 2.x changed
--print --output-format jsonto return a JSON array of messages ([{type:"system",...}, {type:"result", result:"..."}]) instead of a single object. This breaksentire explain --generateand auto-summarization on commit, since the Go code tries to unmarshal the array into a singleclaudeCLIResponsestruct.This PR updates
parseClaudeCLIResult()to try the array format first (extracting the element withtype: "result"), falling back to the single-object format for Claude Code 1.x backward compatibility.Fixes #750
What changed
Typefield toclaudeCLIResponsestructparseClaudeCLIResult()that handles both formatstype: "result"Test plan
mise run fmt— no formatting issuesmise run lint— 0 issuesmise run test— all tests passTestParseClaudeCLIResult— covers both formats, edge cases (no result element, invalid JSON, empty result)TestClaudeGenerator_ArrayResponse— full Generate() flow with 2.x array formatTestClaudeGenerator_ArrayResponseNoResult— array without result element