Skip to content

Claude: show replies to local slash commands - #787

Open
avienor wants to merge 1 commit into
zeronsh:mainfrom
avienor:fix/claude-local-slash-commands
Open

avienor wants to merge 1 commit into
zeronsh:mainfrom
avienor:fix/claude-local-slash-commands

Conversation

@avienor

@avienor avienor commented Oct 4, 2026 •

Copy link
Copy Markdown

Fixes #734 (the bug half; the reset-time display it also asks for is a separate feature and is not part of this PR).

Summary

  • Claude Code answers its local commands (/usage, /context, /cost, /mcp, …) without a model turn: a single assistant frame with model: "<synthetic>" carries the full text, and no text_delta stream events precede it (reproduced with Claude Code 2.1.289). The normalizer took main-thread text only from deltas and kept only tool_use blocks from assistant frames, so these replies rendered empty.
  • Normalizer now tracks whether main-thread text was streamed since the last assistant frame or result. When it wasn't, the frame's text blocks are emitted verbatim as TextDeltas, in block order with tool calls and before AssistantMessageCompleted.
  • Points worth a close look:
    • The flag is reset on the assistant frame and on result, not in rotate_for_steer: a steer echo can rotate the message id between a message's deltas and its frame. The result reset covers turns that end on deltas without an assistant frame (as in the superseded-steers fixture), which would otherwise hide the next command's reply.
    • Only non-empty, untagged deltas set the flag; subagent traffic neither sets nor clears it.
    • Frames carrying an error code keep the current behavior (only the mapped error is shown), so a failure is not reported twice.
    • <synthetic> frames no longer emit ContextUsage (their usage is all zeros, which reset the context meter to 0%) and no longer replace last_model, which the next result uses to look up the context window. Real zero measurements from other frames are untouched.
  • docs/context-usage.md notes that local command replies are excluded from Claude's context measurement.
  • No wire, document or RPC changes; the change is confined to crates/harness/src/claude/normalize.rs.

Known limitations

  • /usage replies are plain text with single newlines; the markdown renderer folds those into one paragraph. /context (markdown) renders well. Making plain-text command output keep its line breaks is a separate change.
  • The gate is a per-message boolean: a hypothetical frame with several text blocks where only some were streamed would not emit the unstreamed ones. No such wire shape has been observed; full reconciliation would need the wire's message/block ids, which the normalizer currently discards.

Test plan

  • New tests in claude::normalize::tests (4 of them fail without the fix):
    • local_command_replies_surface_their_text
    • a_reply_ended_without_its_frame_does_not_hide_the_next_command
    • subagent_traffic_never_gates_main_thread_text
    • synthetic_replies_leave_context_usage_and_model_alone
    • regression guards: streamed_text_is_not_repeated_from_its_assistant_frame, a_steer_between_deltas_and_their_frame_does_not_repeat_text, errored_assistant_frames_keep_only_the_mapped_error
  • cargo test --locked -p zeron-harness (whole crate, including the live Claude fixtures under tests/) passes
  • cargo clippy --locked -p zeron-harness --all-targets: no new warnings (same warning count before and after, none in the touched file)
  • cargo fmt -p zeron-harness --check clean
  • Manual, Linux (Hyprland), Claude Code 2.1.289, dev build with a separate data dir: in one chat, /usage and /context were empty on main and show their full text with this branch
  • Not tested manually on macOS or Windows (the change is platform-independent)

Screenshots

Before (main): /context and /usage produce no reply.

zeron-734-before

After: /context reply rendered.

zeron-734-after-context

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Claude Code answers local commands (/usage, /context, /cost, /mcp...)
without a model turn: one `<synthetic>` assistant frame carries the text,
with no text deltas before it. The normalizer only took text from deltas,
so these replies rendered empty. Emit an assistant frame's text blocks
when none of its text was streamed, and keep the synthetic frame's zeroed
usage and model out of context tracking.

Fixes zeronsh#734
@alexandre-vl

Copy link
Copy Markdown
Contributor

Tried this on top of #1043, which gives a /skill typed as the prompt a chip and a tab when the skill runs in a fork, with Claude Code 2.1.310. A typed /fanout now shows its reply in the chat, alpha bravo right after the fork's chip, and the fork's tab still fills. The two fit together.

Two notes for whoever rebases:

This branch has not been deployed

No deployments
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.

Local slash commands (/usage, /mcp, /context...) display nothing + show reset time for rate limits

2 participants