openclaw - ✅(Solved) Fix Claude Code Agent tool results leak raw JSON to channels [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#66743Fetched 2026-04-15 06:24:36
View on GitHub
Comments
0
Participants
1
Timeline
1
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1

Fix Action

Workaround

Patched locally in dist/execute.runtime-qF-3Rgg9.js. Patch gets overwritten on npm update.

PR fix notes

PR #66819: fix(cli): unwrap nested claude result json

Description (problem / solution / changelog)

Summary

  • Problem: Claude Code sub-agent results can arrive as nested stringified { "type": "result", "result": "..." } payloads, and OpenClaw forwards that raw JSON string to delivery channels instead of the inner text.
  • Why it matters: users see leaked tool/result JSON in Telegram or other channels instead of the actual assistant reply.
  • What changed: recursively unwrap nested Claude result JSON before returning final text from both the JSON and stream-json parsers, and add focused regression tests for both paths.
  • What did NOT change (scope boundary): streaming delta parsing, provider routing, and non-result JSON payload handling.
  • AI-assisted: built with Hermes and locally validated with targeted tests.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #66743
  • Related #
  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: the CLI output parser stopped after extracting the outer Claude result string, even when that string itself was another serialized Claude result payload from a sub-agent/tool handoff.
  • Missing detection / guardrail: there was no regression test covering nested result JSON on the final-output path.
  • Contributing context (if known): Claude Code Agent tool results can be nested more than once before the final delivery text is chosen.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/agents/cli-output.test.ts
  • Scenario the test should lock in: nested Claude { type: "result", result: "..." } payloads unwrap to the innermost plain text for both JSON and stream-json output modes.
  • Why this is the smallest reliable guardrail: the bug is isolated to final CLI output parsing, so parser-level tests cover it without requiring a live Claude CLI run.
  • Existing test that already covers this (if any): existing parseCliJsonl tests covered only single-layer result payloads.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Claude Code sub-agent replies now deliver the actual inner response text instead of a raw nested JSON payload.

Diagram (if applicable)

N/A

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: Ubuntu 24.04
  • Runtime/container: Node + pnpm workspace checkout
  • Model/provider: claude-cli
  • Integration/channel (if any): final CLI output parsing for Claude Code Agent results
  • Relevant config (redacted): targeted parser fixtures only

Steps

  1. Feed parseCliJson a Claude result whose result field is another stringified Claude result payload.
  2. Feed parseCliJsonl a Claude stream-json type: "result" event with the same nested shape.
  3. Verify both parsers return only the innermost plain-text response.

Expected

  • Nested Claude result payloads unwrap to the actual response text before delivery.

Actual

  • Before the fix, the outer parser returned the nested JSON string verbatim.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: pnpm test src/agents/cli-output.test.ts
  • Edge cases checked: nested result JSON in both parseCliJson and Claude stream-json parseCliJsonl paths.
  • What you did not verify: live Telegram delivery against a real Claude Code runtime.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No) Yes
  • Config/env changes? (Yes/No) No
  • Migration needed? (Yes/No) No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: an arbitrary JSON string that happens to look like a Claude type: "result" payload could now be unwrapped.
    • Mitigation: the unwrap is limited to nested objects with the exact Claude result shape and only runs on final text extraction paths.

Changed files

  • src/agents/cli-output.test.ts (modified, +88/-0)
  • src/agents/cli-output.ts (modified, +51/-4)

Code Example

{"type":"result","result":"{\"type\":\"result\",\"subtype\":\"success\",\"result\":\"actual response text\",\"duration_ms\":...,\"usage\":{...}}"}

---

function unwrapNestedAgentResult(text) {
  if (!text || !text.startsWith("{")) return text;
  try {
    const inner = JSON.parse(text);
    if (isRecord(inner) && inner.type === "result" && typeof inner.result === "string")
      return unwrapNestedAgentResult(inner.result.trim()) || text;
  } catch {}
  return text;
}
RAW_BUFFERClick to expand / collapse

Bug

When the Claude Code runtime uses the Agent tool (sub-agent) and the Agent result JSON becomes the assistant's final response text, OpenClaw forwards the raw JSON object to the delivery channel (e.g. Telegram) instead of extracting the .result text field.

What happens

The Claude CLI output looks like:

{"type":"result","result":"{\"type\":\"result\",\"subtype\":\"success\",\"result\":\"actual response text\",\"duration_ms\":...,\"usage\":{...}}"}

parseClaudeCliJsonlResult() in execute.runtime correctly extracts the outer .result string, but that string is itself a stringified JSON Agent result. The inner JSON gets sent verbatim to the channel.

Expected behavior

The inner Agent result JSON should be unwrapped recursively to extract the actual text response.

Where the fix goes

File: src/agents/cli-output.ts (compiled to dist/execute.runtime-*.js)

Functions affected:

  • parseClaudeCliJsonlResult() — the JSONL streaming path
  • parseCliJson() — the fallback JSON path

Fix: Add a recursive unwrapper that detects {"type":"result","result":"..."} patterns in extracted text and unwraps to the inner .result string.

function unwrapNestedAgentResult(text) {
  if (!text || !text.startsWith("{")) return text;
  try {
    const inner = JSON.parse(text);
    if (isRecord(inner) && inner.type === "result" && typeof inner.result === "string")
      return unwrapNestedAgentResult(inner.result.trim()) || text;
  } catch {}
  return text;
}

Apply unwrapNestedAgentResult() to the resultText in both parseClaudeCliJsonlResult and the return value of parseCliJson.

Workaround

Patched locally in dist/execute.runtime-qF-3Rgg9.js. Patch gets overwritten on npm update.

Environment

  • OpenClaw on macOS (Mac Studio M4 Max)
  • Claude Code provider (claude-cli)
  • Delivery channel: Telegram

extent analysis

TL;DR

Apply the unwrapNestedAgentResult() function to recursively extract the actual text response from the Agent result JSON in parseClaudeCliJsonlResult() and parseCliJson().

Guidance

  • Implement the provided unwrapNestedAgentResult() function to handle nested JSON objects and extract the inner .result string.
  • Apply this function to the resultText in both parseClaudeCliJsonlResult and the return value of parseCliJson to ensure consistent unwrapping of Agent results.
  • Verify the fix by checking the output of the Claude CLI and the text received in the Telegram delivery channel to ensure it matches the expected behavior.
  • Consider updating the src/agents/cli-output.ts file and recompiling to dist/execute.runtime-*.js to make the fix permanent and prevent it from being overwritten by npm update.

Example

function parseClaudeCliJsonlResult(jsonl) {
  const resultText = // existing extraction logic
  return unwrapNestedAgentResult(resultText);
}

function parseCliJson(json) {
  const resultText = // existing extraction logic
  return unwrapNestedAgentResult(resultText);
}

Notes

The provided unwrapNestedAgentResult() function assumes that the nested JSON objects follow the {"type":"result","result":"..."} pattern. If other patterns exist, additional handling may be necessary.

Recommendation

Apply the workaround by implementing the unwrapNestedAgentResult() function and applying it to the affected functions, as this provides a direct fix to the issue without relying on external updates.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The inner Agent result JSON should be unwrapped recursively to extract the actual text response.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix Claude Code Agent tool results leak raw JSON to channels [1 pull requests, 1 participants]