Skip to content

fix(cli): capture response from claude-code in maestro-cli send#679

Merged
pedramamini merged 3 commits intoRunMaestro:rcfrom
chr1syy:fix/cli-send-response-capture
Mar 29, 2026
Merged

fix(cli): capture response from claude-code in maestro-cli send#679
pedramamini merged 3 commits intoRunMaestro:rcfrom
chr1syy:fix/cli-send-response-capture

Conversation

@chr1syy
Copy link
Copy Markdown
Contributor

@chr1syy chr1syy commented Mar 28, 2026

Summary

  • maestro-cli send returned response: null for claude-code agents because Claude Code emits the response text in assistant messages, not in the result message's result field (which is empty string)
  • Now accumulates text from assistant messages as a fallback when result field is empty
  • Also flushes the JSONL buffer on process close to handle output lacking a trailing newline (both spawnClaudeAgent and spawnJsonLineAgent)

Test plan

  • Unit tests pass (80/80, including 5 new tests for assistant text fallback, string content, result preference, and buffer flush)
  • Type-check passes
  • Manual e2e test: maestro-cli send <agent-id> "Respond with exactly: CLI capture test OK" now returns success: true with correct response text

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved CLI stream parsing to flush and parse truncated/non-newline-terminated final JSON chunks.
    • Ensure session ID and usage are captured and aggregated; prefer result field but fall back to assistant text when needed.
    • Handle assistant content as string or array and concatenate multiple assistant messages.
  • Tests

    • Expanded agent-spawn tests for stdout buffering, final-chunk parsing, response precedence, and usage/session extraction.

Claude Code emits response text in assistant messages, not in the
result message's `result` field (which is empty). The CLI spawner
only checked `msg.result`, so it always returned `response: null`.

Now accumulates text from assistant messages as a fallback and
flushes the JSON line buffer on process close to handle output
that lacks a trailing newline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 094a6bc7-1ab8-478e-9f1e-c0c7a5be25f2

📥 Commits

Reviewing files that changed from the base of the PR and between e893c8c and 1c5ef2d.

📒 Files selected for processing (2)
  • src/__tests__/cli/services/agent-spawner.test.ts
  • src/cli/services/agent-spawner.ts
✅ Files skipped from review due to trivial changes (1)
  • src/tests/cli/services/agent-spawner.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cli/services/agent-spawner.ts

📝 Walkthrough

Walkthrough

Centralizes per-message parsing in agent spawners, accumulates assistant text as a fallback to result, flushes any buffered non-newline-terminated JSON on child-process close, and ensures session and usage fields are captured once before resolving the final response.

Changes

Cohort / File(s) Summary
Tests
src/__tests__/cli/services/agent-spawner.test.ts
Added cases validating stdout buffering and final-buffer flushing when JSON lines lack trailing newlines; asserted session_id, usage/total_cost_usd capture, result vs assistant-text precedence, handling of assistant content as array or string, and concatenation of multiple assistant messages.
Claude stream handling
src/cli/services/agent-spawner.ts
Added processMessage to centralize extraction (one-time result, accumulate assistant text, one-time session_id, merge usage); switched stdout handling to call it and flush/parse remaining jsonBuffer on close; resolve response with `result
JSON-line agent handling
src/cli/services/agent-spawner.ts
Added processEvent to consolidate per-event logic (init/session id, newline-joined result accumulation, first errorText, usage merging); route parsed events through it and flush final buffered line on close.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble at buffers and chase every byte,
Flushing the crumbs left off in the night.
If result is shy, assistant hops in,
Stitching the lines till the message is thin.
Hooray — no lonely JSON left in my sight! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing response capture from Claude Code agents in maestro-cli send command by handling assistant messages as a fallback.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 28, 2026

Greptile Summary

This PR fixes maestro-cli send returning response: null for Claude Code agents by accumulating text from assistant messages as a fallback when the result message's result field is empty (which is the actual Claude Code SDK behaviour). It also adds a JSON buffer flush on process close for both spawnClaudeAgent and spawnJsonLineAgent, ensuring that output without a trailing newline is still parsed correctly.

Key changes:

  • spawnClaudeAgent: new assistantText accumulator collects text from all assistant-typed stream-json messages; finalResult = result || assistantText is used when resolving the promise.
  • Both spawners: a buffer-flush block in the close handler mirrors the existing data-handler parsing so the last JSON line is never silently dropped.
  • 5 new unit tests cover the buffer-flush path, assistant-text fallback, string vs. array content shapes, and the preference ordering (result field wins when non-empty).

Minor observations (P2, non-blocking):

  • Multiple text blocks from different assistant messages (e.g., intermediate reasoning followed by the final answer) are concatenated without any separator, which could produce joined output like \"Thinking…Final answer\". A \ separator between accumulated blocks would be safer.
  • The buffer-parsing logic is copy-pasted into the close handler for both functions; a small private helper would eliminate the duplication and make future changes easier to land.

Confidence Score: 5/5

Safe to merge — the fix is targeted and well-tested; remaining feedback is P2 style/quality.

No P0 or P1 findings. Both observations (missing text separator and duplicated parse logic) are style/maintainability concerns that do not affect correctness for the primary use-case (single-turn CLI queries where Claude Code emits one final assistant message). The 5 new tests clearly cover all added code paths and the existing suite continues to pass.

No files require special attention.

Important Files Changed

Filename Overview
src/cli/services/agent-spawner.ts Core fix: adds assistantText fallback accumulation and JSON buffer flush on close for both spawnClaudeAgent and spawnJsonLineAgent. Logic is correct; minor concerns around missing text separator and duplicated parsing code.
src/tests/cli/services/agent-spawner.test.ts Adds 5 well-structured tests covering buffer flush, assistant fallback, string content, result preference, and combined scenarios. Coverage is thorough for the new code paths.

Sequence Diagram

sequenceDiagram
    participant CLI as maestro-cli send
    participant Spawner as spawnClaudeAgent
    participant Claude as claude (child process)

    CLI->>Spawner: spawnAgent('claude-code', cwd, prompt)
    Spawner->>Claude: spawn(claude, [--print, --verbose, --output-format stream-json, ...])
    Claude-->>Spawner: stdout data: {"type":"assistant","message":{"content":[{"type":"text","text":"..."}]}}
    Note over Spawner: Accumulate text block → assistantText += text
    Claude-->>Spawner: stdout data: {"type":"result","result":"","total_cost_usd":0.02}
    Note over Spawner: result field is empty → result stays undefined
    Claude-->>Spawner: close(0) [possibly without trailing newline]
    Note over Spawner: Flush jsonBuffer remnant if any
    Note over Spawner: finalResult = result ∥ assistantText ∥ undefined
    Spawner-->>CLI: {success:true, response: assistantText}
Loading

Reviews (1): Last reviewed commit: "fix(cli): capture response from claude-c..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/cli/services/agent-spawner.ts (1)

293-326: Don't swallow non-parse failures in the close-buffer flush.

This try now wraps aggregateModelUsage() and the assistant-content walk too, so an unexpected shape/runtime bug there gets silently discarded and the command resolves with stale data. Only the JSON.parse failure is expected here; handle anything after parsing explicitly instead of absorbing it in the remnant-JSON fallback.

As per coding guidelines, "Do not silently swallow errors with try-catch blocks that only log. Let exceptions bubble up to Sentry for error tracking in production. Only catch and handle expected/recoverable errors explicitly (e.g., NETWORK_ERROR)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/services/agent-spawner.ts` around lines 293 - 326, The current
try-catch around JSON.parse also swallows runtime errors from post-parse
processing (e.g., in aggregateModelUsage, assistant content traversal, or
result/session handling); refactor so only JSON.parse is inside a try that
catches and ignores parse errors, then process the parsed msg outside that catch
(so exceptions in handling—references to resultEmitted/result, assistantText
content assembly, sessionIdEmitted/sessionId, and
aggregateModelUsage(msg.modelUsage, msg.usage, msg.total_cost_usd)—are allowed
to throw and bubble up); keep all existing null/shape checks but remove the
broad try that currently hides unexpected runtime issues.
src/__tests__/cli/services/agent-spawner.test.ts (1)

1067-1166: Cover the JSON-line close-flush path too.

These additions only exercise the Claude branch. The new JSON-line flush logic at Line 530 in src/cli/services/agent-spawner.ts still has no direct test, and the "session_id and usage" case never asserts the buffered usageStats value, so a regression in the other half of this change would still pass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/cli/services/agent-spawner.test.ts` around lines 1067 - 1166,
Add a unit test that exercises the JSON-line close-flush path in spawnAgent so
the JSON-buffer-flush logic in agent-spawner.ts is covered: simulate writing
JSON fragments to mockStdout (e.g., session_id and usage/total_cost_usd emitted
as lines without trailing newline or split across emits), then emit
mockChild.close and await the spawnAgent promise, and assert both
result.agentSessionId (session_id) and the buffered
usage/total_cost_usd/usageStats value are correctly set on the returned result;
locate this behavior around the JSON flush logic in agent-spawner.ts and use the
spawnAgent helper and mockStdout/mockChild used in other tests to reproduce the
close-flush scenario.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/__tests__/cli/services/agent-spawner.test.ts`:
- Around line 1067-1166: Add a unit test that exercises the JSON-line
close-flush path in spawnAgent so the JSON-buffer-flush logic in
agent-spawner.ts is covered: simulate writing JSON fragments to mockStdout
(e.g., session_id and usage/total_cost_usd emitted as lines without trailing
newline or split across emits), then emit mockChild.close and await the
spawnAgent promise, and assert both result.agentSessionId (session_id) and the
buffered usage/total_cost_usd/usageStats value are correctly set on the returned
result; locate this behavior around the JSON flush logic in agent-spawner.ts and
use the spawnAgent helper and mockStdout/mockChild used in other tests to
reproduce the close-flush scenario.

In `@src/cli/services/agent-spawner.ts`:
- Around line 293-326: The current try-catch around JSON.parse also swallows
runtime errors from post-parse processing (e.g., in aggregateModelUsage,
assistant content traversal, or result/session handling); refactor so only
JSON.parse is inside a try that catches and ignores parse errors, then process
the parsed msg outside that catch (so exceptions in handling—references to
resultEmitted/result, assistantText content assembly,
sessionIdEmitted/sessionId, and aggregateModelUsage(msg.modelUsage, msg.usage,
msg.total_cost_usd)—are allowed to throw and bubble up); keep all existing
null/shape checks but remove the broad try that currently hides unexpected
runtime issues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 465ed86a-d7ba-473e-a502-1ccd8aca9f0f

📥 Commits

Reviewing files that changed from the base of the PR and between 4403652 and 537c1b4.

📒 Files selected for processing (2)
  • src/__tests__/cli/services/agent-spawner.test.ts
  • src/cli/services/agent-spawner.ts

…tors

Address review feedback:
- Extract processMessage/processEvent helpers to deduplicate parsing
  logic between stdout data handler and close handler buffer flush
- Add newline separators between accumulated assistant text blocks
  to prevent concatenation without whitespace

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/cli/services/agent-spawner.test.ts`:
- Around line 1083-1103: The test name says it should flush session_id and usage
on close but never asserts usage; update the spec that calls spawnAgent (the
resultPromise/result variable) to also assert the parsed usage from the final
chunk (total_cost_usd: 0.05) is returned by the agent API—e.g. after awaiting
resultPromise add an expectation verifying the usage field on result (match
0.05), using the same result object checked for success/response/agentSessionId
so the test verifies both session_id and usage are flushed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 72b6e20a-a934-4678-81c3-1e76f5b78a05

📥 Commits

Reviewing files that changed from the base of the PR and between 537c1b4 and e893c8c.

📒 Files selected for processing (2)
  • src/__tests__/cli/services/agent-spawner.test.ts
  • src/cli/services/agent-spawner.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/cli/services/agent-spawner.ts

…est assertion

- Narrow try/catch in close handler to only catch JSON.parse failures,
  letting unexpected errors in processMessage bubble up
- Fix trailing whitespace caught by CI prettier
- Add missing usage assertion in buffer flush test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pedramamini pedramamini merged commit 8dbaac7 into RunMaestro:rc Mar 29, 2026
3 checks passed
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