Skip to content

fix: handle Claude Code 2.x JSON array response in summarization#820

Open
areporeporepo wants to merge 2 commits intoentireio:mainfrom
areporeporepo:fix/claude-2x-json-array-response
Open

fix: handle Claude Code 2.x JSON array response in summarization#820
areporeporepo wants to merge 2 commits intoentireio:mainfrom
areporeporepo:fix/claude-2x-json-array-response

Conversation

@areporeporepo
Copy link
Copy Markdown

Summary

Claude Code 2.x changed --print --output-format json to return a JSON array of messages ([{type:"system",...}, {type:"result", result:"..."}]) instead of a single object. This breaks entire explain --generate and auto-summarization on commit, since the Go code tries to unmarshal the array into a single claudeCLIResponse struct.

This PR updates parseClaudeCLIResult() to try the array format first (extracting the element with type: "result"), falling back to the single-object format for Claude Code 1.x backward compatibility.

Fixes #750

What changed

  • Added Type field to claudeCLIResponse struct
  • Extracted parsing logic into parseClaudeCLIResult() that handles both formats
  • Array format (2.x): iterates elements, returns the one with type: "result"
  • Single-object format (1.x): fallback path, unchanged behavior

Test plan

  • mise run fmt — no formatting issues
  • mise run lint — 0 issues
  • mise run test — all tests pass
  • New unit test: TestParseClaudeCLIResult — covers both formats, edge cases (no result element, invalid JSON, empty result)
  • New integration test: TestClaudeGenerator_ArrayResponse — full Generate() flow with 2.x array format
  • New error test: TestClaudeGenerator_ArrayResponseNoResult — array without result element
  • Existing tests pass unchanged (1.x format backward compat)

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>
@areporeporepo areporeporepo requested a review from a team as a code owner March 31, 2026 21:34
Copilot AI review requested due to automatic review settings March 31, 2026 21:34
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

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

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 claudeCLIResponse with a type field and add parseClaudeCLIResult() 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().

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

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
return nil, err
return nil, fmt.Errorf("failed to parse claude CLI response: %w", err)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed — Generate() now wraps with fmt.Errorf for consistent grep-able prefix.

Comment on lines +167 to +171
for _, r := range responses {
if r.Type == "result" && r.Result != "" {
return r.Result, nil
}
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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")
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Comment on lines 69 to 75
// 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"`
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed — doc comment now uses valid JSON with quoted keys.

name: "single object with empty result",
input: `{"result": ""}`,
expectError: "empty result",
},
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
},
},
{
name: "array with empty result element",
input: `[{"type":"system"},{"type":"result","result":""}]`,
expectError: "empty result",
},

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Summary generation fails: cannot unmarshal array from Claude Code 2.x JSON output

2 participants