Em dash guard boundary: what to review โ
| Field | Value |
|---|---|
| Branch | feat/admin-and-merchant-portals, draft PR #976: feat(admin): portal navigation, financials and monitoring, plus the agent-process work behind them, not merged |
| Project | README.md |
| Issue | #967: key-conventions.md said the em dash guard never sees a heredoc; it blocked one twice today |
| Servers you need | None. Nothing to click, and no code changed. This is a documentation correction |
| Local origins | Not applicable |
Is there anything to look at? โ
One sentence, in one rule file, and its four synced copies. โ
- The source is
.agents/rules/project/key-conventions.md. The copies areAGENTS.md,.cursor/rules/key-conventions.mdcand.github/instructions/key-conventions.instructions.md. guard-em-dash.shitself was NOT changed. Nothing about how the hook behaves is different.- Read the corrected sentence: it is quoted in full in
README.md, and it is already live inAGENTS.mdunder Key Conventions.
What changed here? โ
The rule said the hook never sees a heredoc. That was false, and three sessions built on it. โ
- The
.claude/settings.jsonmatcher isWrite|Edit|Bash, and the Bash branch scans any command whose text CONTAINSgitorgh. A heredoc feeding agit commitmessage or aghPR body IS caught, which is exactly why a dispatched agent got blocked twice that day before ever touching a file. - Three sessions read the wrong sentence, treated heredoc writes as fully unguarded, and a compensating control got built on that false premise.
The real boundary was established by TESTING before anything was written. โ
- Ten crafted payloads piped into the live hook script, reading the real exit code, with controls both ways.
- Caught: a literal dash in a
git commitcommand text. - Not caught, all verified: a non-git
echo; agit logwhose format string comes from command substitution;git commit -m "$VAR";git checkout HEAD -- filerestoring tracked content;git apply;cpandmv; a program's own output redirected to a file. - One thing is reasoned rather than tested and says so: whether a literal em dash inside a non-git heredoc body would be scanned. It could not be tested without typing the character, which this task's own constraint forbade.
- Corrected 2026-08-30: "names
gitorgh" was too narrow. The matcher is a substring glob, sothrough,high,enoughanddigitare scanned too, all four re-verified against the live hook. The "not caught" list above still holds, because none of those commands carries either substring. The guard errs safe and was again left alone.
What is deliberately NOT done? โ
The guard's behaviour was left alone on purpose. โ
#945: A text-scanning guard cannot tell content from a pattern that matches content is the same root cause in the opposite direction: a false positive, where the guard blocks agreppattern that searches FOR the character. This project's finding is a false negative.- That issue's own caution against changing the guard without your word is why only the documentation moved.
A live end-to-end proof in a scratch git repo was attempted and abandoned. โ
- The PM lane guard refused the command, and the session's
CLAUDE_ALLOW_PM_BUILD=1authorization was scoped tokey-conventions.mdand the sync output only. The override was not used to push past it. - The ten direct hook invocations already exercise the real code path and were treated as sufficient.
What open findings touch this project? โ
- Nothing from
../merchant-and-venue-dashboards/branch-review-0828.md. - One thing worth knowing about the review of this work itself: a second guard,
guard-backtick-body.sh, blocked the first commit attempt because live$( )substitution inside a double-quoted-margument is a shell-injection shape. Its own message names the fix, and the commit went through with-Fagainst a pre-checked message file. Both guards did their job.
What issue does each piece close? โ
| Piece | Issue | State |
|---|---|---|
| The corrected sentence and its four synced copies | #967: key-conventions.md said the em dash guard never sees a heredoc; it blocked one twice today | Fixed on branch |
| The opposite failure direction in the same guard | #945: A text-scanning guard cannot tell content from a pattern that matches content | Open, guard deliberately untouched |