fix: render local-command-stdout messages instead of dropping them#649
Open
Wandering-Consciousness wants to merge 1 commit into
Conversation
The live "user"/"assistant" handler in acp-agent.ts drops every SDK message whose string content contains <local-command-stdout>. The branch was added to tolerate /compact's malformed output, but its side-effect is silencing every successful slash command invocation — custom user-defined skills (whose expanded bodies arrive wrapped in <command-*> + <local-command-stdout> markers) and built-in commands that emit textual output through these tags. From an ACP client the symptom is: type `/foo`, get an empty thread; the model receives the expansion and responds, but the user message containing the expansion never reaches the UI. The repo already ships `stripLocalCommandMetadata`, which returns null for marker-only payloads and the stripped prose otherwise; it is already wired into session replay/load (around line 1385) but not into the live handler. Wire it in: strip the markers and route what remains through the existing `toAcpNotifications` path, preserving the message's role. Pure-marker payloads (e.g. /compact) still no-op via the null path, so the existing /compact integration test in src/tests/acp-agent.test.ts continues to pass. Refs agentclientprotocol#624, agentclientprotocol#642.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI disclaimer: I identified this bug after noticing that custom skills produced no output within the Zed Thread/Claude Agent UI. I used Claude to investigate the issue and develop the fix. I have manually verified the equivalent fix against an installed
@zed-industries/claude-code-acp@0.16.1(the deprecated package Zed currently ships): custom slash commands that previously produced empty threads now render the model's response correctly after Zed restart.Summary
The live
case "user" / "assistant"handler insrc/acp-agent.tsdrops every SDK message whose string content contains<local-command-stdout>. The branch was added to tolerate/compact's malformed output, but its side-effect is silencing every successful slash command invocation — custom user-defined skills (whose expanded bodies arrive wrapped in<command-*>+<local-command-stdout>markers) and built-in commands that emit textual output through these tags.Symptom in ACP clients
User types
/some-command(custom skill, or one of the affected built-ins). Thread renders the user's/some-commandbubble at the top, then nothing — no expanded skill body, no model response. Under the hood the model has actually received the expansion and produced a turn, but the user message carrying the expansion never reaches the UI, and the cascade of subsequent assistant content gets visually decoupled.In CLI Claude Code the same expansion is printed to the terminal, so the asymmetry is purely client-side.
Fix
This repo already ships
stripLocalCommandMetadata(and its underlyingstripMarkerTagshelper) — they returnnullfor marker-only payloads and the stripped prose otherwise. They are already wired into session replay/load (acp-agent.ts:1385) but not into the live handler.The change wires the same helper into the live handler: strip the markers and route what remains through the existing
toAcpNotificationspath, preserving the message's role. Pure-marker payloads (e.g./compact's) still no-op via thenullbranch, so the existing/compact worksintegration test insrc/tests/acp-agent.test.tscontinues to pass.Why the previous behaviour was conservative-but-wrong
The inline comment notes the filter exists for
/compact's SDK-side malformed output. That tradeoff swallows a much larger class of legitimate output (anything that uses<local-command-stdout>to wrap real prose) to avoid one specific command's noise. WithstripLocalCommandMetadataavailable, the conservative behaviour can target exactly the empty-after-strip case (stillnull→ no-op) without affecting messages that have real prose alongside markers — which is exactly the regression case the helper's tests were written to cover (seesrc/tests/acp-agent.test.ts:1145-1163, "in the original bug report the entire /model preamble and the user's real 'hi' prompt were concatenated into a single message. We want to strip the marker tags and preserve the real prose, not drop the whole message.").The fix applies that same principle to the live handler, where the helper is most needed.
Refs
zed-industries/claude-code-acp#624— "'Skill' Session Updates missing when invoked via slash command". The reporter correctly diagnoses the mechanism: "Claude just attaches the skill directly to context when the /command is used, which means the skill loader isn't used." The skill body arrives wrapped in markers and gets dropped by this filter.zed-industries/claude-code-acp#642— built-in slash commands (/usage,/status,/model,/memory,/permissions,/agents,/mcp) emit no output when invoked. Same root cause: their output arrives in messages tagged with<local-command-stdout>and is filtered out.Both upstream issues remain open against the old repo
zed-industries/claude-code-acpand have no comments / linked PRs yet. Cross-linking here so this PR can be the closing reference for both.Test plan
npm run check(lint + format) — passesnpm run build(tsc) — passesnpx vitest run src/tests/acp-agent.test.ts -t "stripLocalCommandMetadata"— 8 passed/compact worksintegration test — needs a CLI environment to run; relies on thenullbranch preserving the no-op behaviour for marker-only payloads, which the helper tests already cover🤖 Generated with Claude Code