Skip to content

ruff/biome/bash-format: two JSON documents on stdout when one run both rewrites and reports findings #3406

Description

@kyle-sexton

Context

lib/hook-utils.sh:75-78 documents the invariant: Claude Code parses the hook's whole stdout as a single JSON document, so a run that has both a user-channel message and agent-channel context "must compose them here rather than print twice." Three formatter hooks violate it on the rewrite-plus-findings path:

  • plugins/ruff-format/hooks/ruff-format.sh: hook::emit_system_message (rewrite disclosure, ~line 243) prints one JSON document; when the verify pass then returns findings (RC -eq 1), hook::ctx_flush (~line 272) prints a second.
  • plugins/biome-format/hooks/biome-format.sh: same shape, disclosure at line 221, hook::ctx_flush at line 252 (and 282).
  • plugins/bash-format/hooks/bash-format.sh: disclosure at line 240, then hook::emit_channels at line 300/302 prints a second document on the same run.

Any single run that both rewrites the file and reports findings puts two JSON documents on stdout. #3401 (a7f9537) named this exact violation "pre-existing ... in the same file" while fixing powershell-format; these three siblings still carry it. No test in ruff/biome/bash covers rewrite-plus-findings composition: jq -s length single-document assertions exist only in powershell-format.test.sh:280,435, typos-format.test.sh:322, and bash-format.test.sh:613 (the notice-plus-findings path, which already routes through hook::emit_channels).

Verified against the code on 2026-08-27.

Proposed work

  • Compose the rewrite disclosure into the single end-of-run document (the hook::emit_channels path powershell now uses) in all three hooks, instead of emitting it immediately.
  • Add a rewrite-plus-findings case to each suite asserting stdout parses as exactly one JSON document (jq -s 'length == 1') carrying both channels.

Acceptance criteria

  • A run that rewrites and reports findings emits exactly one JSON document carrying both systemMessage and additionalContext, in each of ruff-format, biome-format, bash-format.
  • A rewrite-only run and a findings-only run each still emit exactly one document.
  • Each suite has a non-vacuous jq -s composition assertion for the rewrite-plus-findings path.

References

  • lib/hook-utils.sh:75-78 (the invariant)
  • plugins/ruff-format/hooks/ruff-format.sh:243,272 · plugins/biome-format/hooks/biome-format.sh:221,252 · plugins/bash-format/hooks/bash-format.sh:240,300-302
  • fix(powershell-format): release the _ps_before snapshot and disclose a rewrite on the tool-break arm #3401 / commit a7f9537 (powershell fix that named the sibling violations)
  • Related architecture finding: the disclosure protocol is hand-maintained per plugin; a deepening candidate consolidating it is filed separately.
  • Found by the /architecture:improve deepening scan (2026-08-27); claim reproduced against the code before filing.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: highSignificant impact, or blocks an imminent release; staff this cycle.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions