Skip to content

feat(markdown-format): report end-of-turn lint findings by default without continuing the turn - #11756

Merged
kyle-sexton merged 7 commits into
mainfrom
work-resolve-issue/issue-11502-41189504991-1
Nov 1, 2026
Merged

kyle-sexton merged 7 commits into
mainfrom
work-resolve-issue/issue-11502-41189504991-1

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

Closes #11502

Summary

#11502 (part of #9154, gate G9) turns markdown-format's end-of-turn check from opt-in into a report by default. With markdown_format_end_of_turn unset, Markdown lint findings in the uncommitted changes reach the user as a warning and the turn ends. true keeps today's hand-back to Claude. false runs no check. The issue lane built the change and stopped only for hook-budget Rule 1, because a Stop row that now runs on every turn end needs its spawn count and wall time measured under strace. This host run measured it and added the ratchet.

Fix

  • From the lane run (c75f25c):
    • markdown-format.sh --end-of-turn: unset → systemMessage with the findings, exit 0; true → exit 2, at most once per turn (stop_hook_active).
    • hooks.json: the gate is now --run-if-unset-or-true.
    • The option has no default, and its description, README and setup skill state the new default.
    • Tests cover AC1–AC4; docs/contracts/hook-budget and check-placement updated.
  • This run:
    • .performance/ratchets.json holds the new default clean-tree path at 20 spawns (markdown-format-stop-end-of-turn-default-clean-spawns).
    • plugins/markdown-format/README.md states the measured cost, replacing "not measured yet".

Verification

  • scripts/hook-census.sh under strace, default path on a clean tree: 20 spawns (11 creations, 9 execs), reproduced twice. The option-off ratchet stays at 1.
  • hyperfine -N, default against option off:
    • WSL2: 45.6 ms against 25.3 ms, 60 runs each.
    • Windows Git Bash 5.3.15: 447.5 ms against 90.6 ms, 40 runs each.
    • So about 357 ms per turn end on Windows. That is inside the 2,000 ms Stop wall and the 1 s per-fire ceiling.
  • scripts/check-affected.sh --tests on the committed head: 14 passed, 2 skipped.
  • Left out: the lane's open question of whether /plugin configure saves false for this boolean when a user edits another option, which would turn the report off for them. It needs an interactive Claude Code session, so it is not checked here.

Related

🤖 Generated with Claude Code

melodic-automation-lanes Bot and others added 5 commits October 31, 2026 10:21
Lane-Activity: work-resolve-issue/resolve-issue
…op path

With markdown_format_end_of_turn unset, the Stop row now runs the check on
every turn end. hook-census.sh under strace puts the clean-tree path at 20
spawns (11 creations, 9 execs), and hyperfine at 45.6 ms mean against 25.3
ms with the option false, on WSL2. A ratchet holds the 20-spawn ceiling and
the README states the figures (hook-budget Rule 1).

Refs: #11502

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urn path

Refs: #11502

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review November 1, 2026 00:13
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T00:15:01.903660Z c6def58 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-security-review: 1 inline finding(s) on c6def582aaeed4ff6c7629aaa9c0201aee54fadb; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 5788c70eeb53e1b418b0c6de29c45a4b3c06150b: read 13 of 13 changed file(s); 1 finding(s), 1 as inline comments.

Security review: PR #11756 (markdown-format end-of-turn report on by default)

I read every hunk of all 13 changed files. No changed file matches a TRUSTED PATHS: glob. The change touches no trust control: no trusted-actor list, lane token, kill switch, broker, merge hold or workflow.

Manifest items (plugins/markdown-format/.claude-plugin/plugin.json):

  • manifest description: pass. It's in the inert set.
  • manifest userConfig.markdown_format_end_of_turn.description: pass. It's in the inert set.
  • manifest userConfig.markdown_format_end_of_turn.default: flag. "default": false is removed, so users who never set the option now get the Stop check on update. Inline comment, IMPORTANT.

What I checked and found clean

  • Gate semantics. In exec-bash.mjs, --run-if-unset-or-true opens only when the value is unset or exactly true (exec-bash.mjs#L177-L182). Setting false still closes the row before bash starts. markdown_format_enabled=false still wins, and the new tests cover both cases.
  • Output channel. In the default mode, findings go only to the user, through hook::emit_system_message → hook::emit_channels. That path escapes backslash, quote, CR, LF and tab, and strips the other C0 bytes (hook-utils.sh#L89-L100, #L119-L135). A file name or lint message from the repository therefore can't inject extra hook JSON keys such as decision or continue. In this mode, no lint text reaches the model. The true hand-back to the model through stderr already existed and is unchanged.
  • Instruction surfaces. The skills/setup/SKILL.md and README edits describe the new default. They delete or soften no guardrail. The instruction exception register was not needed: nothing protected was removed.
  • Ratchets and tests. The .performance/ratchets.json and test edits are measurement and coverage only.

Spend: none. The default Stop row adds about 20 local process spawns per turn end, which is local compute with no charge.

No embedded instructions in the PR context. No settled threads were supplied.

critical-findings: 0

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed 5788c70eeb53e1b418b0c6de29c45a4b3c06150b: read 13 of 13 changed file(s); 0 finding(s), 0 as inline comments.

Code review: PR #11756 (markdown-format end-of-turn report by default)

I read all 13 changed files in .claude-lane/pr.diff, plus the PR context and closing issue #11502. I found nothing to block or flag. None of the changed files match a trusted-path glob.

What I checked

  • Gate and script agree on what "unset" means. In exec-bash.mjs, --run-if-unset-or-true passes undefined, "" and "true" (exec-bash.mjs#L162). markdown-format.sh hands findings back to Claude (exit 2) only when the value is exactly true. Unset and empty both reach the systemMessage path, and a test covers the empty case.
  • The report outputs only a systemMessage. hook::emit_system_message calls hook::emit_channels "" "" "$1", which prints a JSON object with only a systemMessage key (hook-utils.sh#L119-L135). There is no decision, no additionalContext and no exit 2, which is what the issue's "Key interfaces" section asks for.
  • Missing markdownlint-cli2 stays quiet. When an agent runs it (CLAUDECODE=1), the FAIL: … missing. Install: line is caught as before. When a person runs it, checks_require prints a NOTICE and exits 0, so the row exits quietly (check-lib.sh#L211-L217). Tests cover both the default and true settings.
  • scripts/hook-census.sh already accepts the --env K=V flag used by the reworked option-off ratchet and its test.

Closing issue #11502 acceptance criteria

  • AC1 is met: markdown-format-end-of-turn.test.sh checks exit 0, empty stderr, that systemMessage is the only key, and that the message carries the finding.
  • AC2 is met: on a clean tree with the option unset, the row prints nothing.
  • AC3 is met: with true, a finding exits 2, and stop_hook_active keeps it quiet for the rest of the turn.
  • AC4 is met: false runs no check, and neither does MARKDOWN_FORMAT_ENABLED=false.
  • AC5 is met: the option description, both README tables and the prose say unset is the default. markdown-format.test.sh asserts that the option no longer declares a default.

Plugin manifest

  • manifest userConfig.markdown_format_end_of_turn.default: pass. The default was removed, and the PR says why: unset is now the reporting state, following Approve the must-not-break gate set, defaults and reviewer rule from #5678's design proposal #9154 Decision 2.
  • manifest version: pass. The changelog fragment declares bump: minor for a default-behavior change in a 0.x plugin.
  • manifest description: pass. It now names the turn-end report. docs/catalog.md matches, and the marketplace entry has no description to update.

Notes (not findings)

  • The PR says (I did not verify) that /plugin configure may save false for this boolean when a user edits a different option, which would quietly turn the report off. The PR lists this as unchecked because it needs an interactive session. It is worth confirming before release.
  • Cost: this spends no API credits or billing. It adds local hook time on every turn end: the PR reports 20 spawns, about 20 ms on WSL2 and about 357 ms on Windows. These are the author's measurements; I did not rerun them.

I ran no tests myself. The test results in the PR description are the author's and I have not verified them.

@kyle-sexton

Copy link
Copy Markdown
Member Author
claude-review finding (c6def58) Verdict Evidence
The manifest description omits the default end-of-turn report VALID, fixed plugins/markdown-format/.claude-plugin/plugin.json:4 now reads "…findings are reported at commit and, unless markdown_format_end_of_turn is false, at each turn end for the uncommitted changes." docs/catalog.md was regenerated to match. Fixed in 3e931bf, pushed in 5788c70, which also merges main for the stale-base overlap on docs/contracts/hook-budget/README.md.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-security-review: 1 inline finding(s) on 5788c70eeb53e1b418b0c6de29c45a4b3c06150b; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 0b131e8 Nov 1, 2026
48 checks passed
@kyle-sexton
kyle-sexton deleted the work-resolve-issue/issue-11502-41189504991-1 branch November 1, 2026 00:50
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.

feat(markdown-format): report end-of-turn lint findings by default without continuing the turn

1 participant