Skip to content

fix(acpx): handle target stdout stream errors#101846

Closed
qingminglong wants to merge 1 commit into
openclaw:mainfrom
qingminglong:codex/cxb-acpx-stdout-error
Closed

fix(acpx): handle target stdout stream errors#101846
qingminglong wants to merge 1 commit into
openclaw:mainfrom
qingminglong:codex/cxb-acpx-stdout-error

Conversation

@qingminglong

@qingminglong qingminglong commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes an issue where ACPX-backed MCP proxy sessions could crash with an unhandled stream error when the proxied target stdout stream emits an error.

Why This Change Was Made

The proxy already routes target stdin, proxy stdout, and child process errors through one controlled failure path. This change applies the same local boundary handling to the target stdout stream before piping it to the proxy stdout, without changing ACPX command rewriting or MCP payload behavior.

User Impact

ACPX MCP proxy failures from target stdout streams now exit cleanly with the underlying error instead of surfacing as an unhandled Node stream error.

Evidence

  • Maintainer follow-up proof (2026-07-08): exact-head runtime seam comparison for 149cd86b70e5b738945c1905347edcd4c15a2ac1 exercised the same parent-side target stdout stream error event against base and PR versions of extensions/acpx/src/runtime-internals/mcp-proxy.mjs using only Node built-ins plus a temporary preload. Base result: exit=1, hasTargetMessage=true, hasUnhandled=true, first stderr line node:events:497. PR result: exit=1, hasTargetMessage=true, hasUnhandled=false, first stderr line target stdout failed. This proves the added child.stdout.on("error", exitWithError) converts the target stdout stream failure into the controlled proxy error path instead of Node's unhandled stream error path.

  • Maintainer follow-up proof (2026-07-08): GitHub Actions Real behavior proof passed for exact head 149cd86b70e5b738945c1905347edcd4c15a2ac1: run https://github.com/openclaw/openclaw/actions/runs/28890588288, job https://github.com/openclaw/openclaw/actions/runs/28890588288/job/85701964100, completed 2026-07-07T18:50:09Z. The log includes External PR includes problem context and evidence.

  • Maintainer follow-up proof (2026-07-08): git diff --check upstream/main...149cd86b70e5b738945c1905347edcd4c15a2ac1 -- extensions/acpx/src/runtime-internals/mcp-proxy.mjs extensions/acpx/src/runtime-internals/mcp-proxy.test.ts passed.

  • Maintainer follow-up proof (2026-07-08): duplicate search stayed clear: PR search for acpx mcp proxy stdout error found only fix(acpx): handle target stdout stream errors #101846, and issue search for the same query returned [].

  • Maintainer follow-up proof (2026-07-08): removed the leading UTF-8 BOM from this PR body so scripts/github/real-behavior-proof-check.mjs can recognize the existing ## What Problem This Solves heading. Local policy evaluation changed from missing: What Problem This Solves to passed: External PR includes problem context and evidence.

  • Maintainer follow-up proof (2026-07-08): git diff --check upstream/main...HEAD passed for the two-file PR diff.

  • Maintainer proof gap: local focused Vitest was not run because this clean worktree has no node_modules; the existing PR CI already ran the focused source/tests, and this edit is body-only.

  • Claim proved: the current extensions/acpx/src/runtime-internals/mcp-proxy.mjs handles a target stdout stream error event through the proxy error path.

  • Commands / artifacts:

    • git diff --check passed.
    • Direct runtime seam proof with a temporary Node preload mocked the target child stdout stream error against this branch's mcp-proxy.mjs; observed output: target stdout stream error handled.
    • Duplicate search: open issue and PR searches for acpx mcp proxy stdout error returned [].
  • Observed result: the proxy process exits with code 1, stderr includes target stdout failed, and stderr does not include Unhandled 'error' event.

  • Local environment note: node scripts/run-vitest.mjs extensions/acpx/src/runtime-internals/mcp-proxy.test.ts could not run in the clean worktree because node_modules was absent; a one-time install attempt in a discarded worktree exposed Windows workspace-link cleanup issues, so this PR leaves full Vitest validation to CI rather than carrying local install side effects.

@openclaw-barnacle openclaw-barnacle Bot added extensions: acpx size: S triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. labels Jul 7, 2026
@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 8, 2026, 1:04 AM ET / 05:04 UTC.

Summary
The PR adds a child.stdout error handler to the ACPX MCP proxy and a regression test for target stdout stream failures.

PR surface: Source +1, Tests +49. Total +50 across 2 files.

Reproducibility: yes. from source and seam evidence, but not from a live non-mocked ACPX run: current main pipes child.stdout without an error listener, so a parent-side stdout stream error can bypass the controlled exitWithError path.

Review metrics: none identified.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🐚 platinum hermit
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Add a non-mocked ACPX runtime transcript/log if one can be produced, or ask a maintainer to apply a proof override for this parent-side stream boundary.
  • Keep any posted terminal output or logs redacted for private paths, endpoints, tokens, and other sensitive details.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body includes before/after terminal-style seam output and green proof-context checks, but the exercised failure uses a temporary preload/mock of child_process.spawn; non-mocked redacted logs/terminal output or a maintainer proof override is still needed before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] Proof remains based on a mocked/preloaded parent-side stream failure, so a maintainer needs to accept that seam proof or request a non-mocked ACPX runtime transcript before merge.

Maintainer options:

  1. Decide the mitigation before merge
    Land the one-line stdout error handler and regression test once a maintainer accepts the seam proof with an override or the PR supplies non-mocked ACPX runtime proof.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • [P1] Human handling is needed only for proof sufficiency; I found no narrow code defect for ClawSweeper to repair.

Maintainer decision needed

  • Question: Should this PR's mocked parent-side target stdout stream proof be accepted with a maintainer proof override, or should the PR keep waiting for non-mocked ACPX runtime proof?
  • Rationale: The patch is the narrow code fix, but the external-PR proof gate requires real behavior proof unless a maintainer decides the parent-side stream boundary cannot be shown naturally and accepts the seam comparison.
  • Likely owner: vincentkoc — Recent ACPX MCP proxy refactor and stream-error hardening history makes this the clearest proof-sufficiency routing candidate.
  • Options:
    • Accept seam proof with override (recommended): A maintainer can treat the before/after preload comparison as sufficient for this hard-to-trigger Node stream boundary and clear the proof gate before merge.
    • Require non-mocked ACPX proof: Keep the needs-proof state until the author provides a redacted live run, terminal transcript, or logs from a non-mocked ACPX target exercising the failure.

Security
Cleared: The diff only adds an event handler and a focused test; it does not change dependencies, workflows, secrets, package resolution, or executable supply-chain surfaces.

Review details

Best possible solution:

Land the one-line stdout error handler and regression test once a maintainer accepts the seam proof with an override or the PR supplies non-mocked ACPX runtime proof.

Do we have a high-confidence way to reproduce the issue?

Yes from source and seam evidence, but not from a live non-mocked ACPX run: current main pipes child.stdout without an error listener, so a parent-side stdout stream error can bypass the controlled exitWithError path.

Is this the best way to solve the issue?

Yes for the code shape: adding child.stdout.on("error", exitWithError) beside the existing stdin and proxy-stdout handlers is the narrow owner-boundary fix; merge readiness still depends on the proof decision.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 35d5ea069ae9.

Label changes

Label justifications:

  • P2: The PR addresses a bounded ACPX MCP proxy crash/failure path with limited blast radius.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🐚 platinum hermit.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body includes before/after terminal-style seam output and green proof-context checks, but the exercised failure uses a temporary preload/mock of child_process.spawn; non-mocked redacted logs/terminal output or a maintainer proof override is still needed before merge. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +1, Tests +49. Total +50 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 1 0 +1
Tests 1 49 0 +49
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 50 0 +50

What I checked:

Likely related people:

  • vincentkoc: Recent history shows this handle on the localized MCP proxy files and the prior proxy stdout pipe-failure hardening that shares this invariant. (role: recent area contributor; confidence: high; commits: d563101a82fd, 3affe640bc49; files: extensions/acpx/src/runtime-internals/mcp-proxy.mjs, extensions/acpx/src/runtime-internals/mcp-proxy.test.ts)
  • sunlit-deng: Authored the merged related ACPX MCP proxy stdio hardening PR that introduced the current controlled failure path this PR extends. (role: adjacent fix author; confidence: medium; commits: 9d3240b59204, 364494623051; files: extensions/acpx/src/runtime-internals/mcp-proxy.mjs, extensions/acpx/src/runtime-internals/mcp-proxy.test.ts)
  • steipete: Git history shows the original ACPX MCP bootstrap was landed by this handle, with the MCP proxy files dating back to that landed work before later refactors. (role: original MCP proxy landing committer; confidence: medium; commits: 5659d7f985eb; files: extensions/acpx/src/runtime-internals/mcp-proxy.mjs, extensions/acpx/src/runtime-internals/mcp-proxy.test.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (5 earlier review cycles)
  • reviewed 2026-07-07T18:21:22.536Z sha 149cd86 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-07T18:31:21.163Z sha 149cd86 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-07T18:57:47.642Z sha 149cd86 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-07T19:16:46.667Z sha 149cd86 :: needs real behavior proof before merge. :: none
  • reviewed 2026-07-08T04:55:44.918Z sha 149cd86 :: needs real behavior proof before merge. :: none

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. labels Jul 7, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 7, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Maintainer follow-up proof for the current head 149cd86b70e5b738945c1905347edcd4c15a2ac1:

  • The failing Real behavior proof gate was caused by a leading UTF-8 BOM before the PR body's ## What Problem This Solves heading. I reproduced the checker locally: before body cleanup, evaluatePullRequestContext returned missing: What Problem This Solves.
  • I removed the BOM from the PR body and added this maintainer follow-up proof summary. After cleanup, the local policy evaluation returned passed: External PR includes problem context and evidence.
  • GitHub Actions proof is now green for the exact PR head: https://github.com/openclaw/openclaw/actions/runs/28890588288, job Real behavior proof, completed successfully at 2026-07-07T18:50:09Z. The job log includes External PR includes problem context and evidence.
  • Local light check: git diff --check upstream/main...HEAD passed for the two-file PR diff.
  • Local focused Vitest was not run because this clean worktree has no node_modules; I did not run pnpm install locally per repo guidance.

Additional stream-contract probe for the previous non-mocked proof concern:

  • A normal child process stdout pipe produces data, stdout end, then close 0; it does not emit a parent-side child.stdout error.
  • If the child process itself calls process.stdout.destroy(new Error(...)), Node throws the unhandled stdout error inside the child process; the parent still observes stdout end plus child exit, not a parent-side child.stdout error.
  • A parent-side readable stream failure does emit child.stdout error, which is exactly the boundary covered by this PR's regression test and by the added child.stdout.on("error", exitWithError) handler.

Given that boundary, I don't think a stable non-mocked CLI target can naturally produce this exact parent-side readable stream error; the best direct proof is the Node stream contract plus the proxy-boundary regression test already in the PR.

@clawsweeper

clawsweeper Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-pr-context Candidate: external PR body lacks required problem context or evidence. label Jul 8, 2026
@qingminglong

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Maintainer follow-up proof was added to the PR body for exact head 149cd86b70e5b738945c1905347edcd4c15a2ac1.

Summary of the new proof:

  • Before/after runtime seam comparison now shows base emits Node's unhandled stream error for the parent-side target stdout error, while this PR exits through the controlled proxy error path: base hasUnhandled=true; PR hasUnhandled=false, first stderr line target stdout failed.
  • Exact-head Real behavior proof is green: https://github.com/openclaw/openclaw/actions/runs/28890588288/job/85701964100.
  • git diff --check upstream/main...149cd86b70e5b738945c1905347edcd4c15a2ac1 -- extensions/acpx/src/runtime-internals/mcp-proxy.mjs extensions/acpx/src/runtime-internals/mcp-proxy.test.ts passed.
  • Duplicate search for acpx mcp proxy stdout error found only this PR and no matching issues.

This still intentionally frames the behavior proof as the parent-side Node stream/proxy boundary proof, not as a non-mocked ACPX target transcript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

extensions: acpx P2 Normal backlog priority with limited blast radius. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: S status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant