standards: a negated close-keyword still auto-closes the issue — document both vectors and enforce in pre-push
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 1
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 7
Description
Problem
standards/CLAUDE.md's Branch Naming and PR Linking section currently documents only the half of this that causes harm:
PR linking: When a PR resolves an existing issue, include
Closes #<number>orFixes #<number>in the PR body so GitHub auto-closes the issue when the PR merges into the default branch.
It says nothing about the inverse — how to reference an issue without closing it. GitHub links an issue when it finds a close-keyword (close/closes/closed, fix/fixes/fixed, resolve/resolves/resolved) immediately followed by an issue reference, and it does not parse the surrounding sentence. A heading of the shape Scope — this does not close <ref> links <ref> and closes it on merge.
This bit five times in a single afternoon on TimZander/AudioClassifier, every time in text written specifically to prevent the link.
Evidence
| PR | Text | Effect |
|---|---|---|
| AudioClassifier#195 | ## Scope — this does not close <ref> |
would have closed the issue |
| AudioClassifier#183 | ## Scope — this does not fully close <ref> |
would have closed the issue |
| AudioClassifier#183 | commit message: Closes <ref> |
still live — body says "leaves it open" |
| AudioClassifier#195 | commit messages: Does not close <ref>, Records but does not fix <ref> |
still live |
| AudioClassifier#199 | commit message quoting the examples above | the PR documenting the trap carried it |
The issues at risk were not trivial: one is a hosting-provider dashboard setting that no code change can fix, and another needs three separate PRs before it's actually done. Auto-closing either drops live work under a green checkmark.
The part that makes it nasty: two vectors, one of them invisible
- PR body — linked before merge, visible via
closingIssuesReferences. - Commit messages — when the repo squashes with
squash_merge_commit_message: COMMIT_MESSAGES(AudioClassifier does), every commit message on the branch is concatenated into the squash body on the default branch, where GitHub honors the keyword.closingIssuesReferencescannot see this — it reportsNONEwhile the trap is live.
Checking only the body is how AudioClassifier#183 was reported clean while Closes <ref> sat in its commit message. The parser reads the default branch, not the PR.
Proposed — add to standards/CLAUDE.md under "Branch Naming and PR Linking"
Not closing an issue is a thing you have to say carefully. GitHub links a close-keyword followed by an issue reference and does not parse negation —
this does not close #Nregisters as closing#N.
- To close:
Closes #<n>on its own line in the PR body.- Otherwise: never put a keyword adjacent to a number, in the body or a commit message. Use
leaves #<n> open,Part of #<n>, or name the relationship. When quoting an example of this trap, redact the number.- Two vectors. The body is visible via
closingIssuesReferences; commit messages are not, and reach the default branch via the squash body whensquash_merge_commit_message: COMMIT_MESSAGES. Check both:gh api graphql -f query='query { repository(owner:"OWNER", name:"REPO") { pullRequest(number:N) { closingIssuesReferences(first:5){nodes{number title}} } } }' git log origin/main..HEAD --format='%h %s%n%b' | grep -inE '\b(clos(e|es|ed)|fix(es|ed)?|resolv(e|es|ed))[[:space:]]+#[0-9]+'A pushed commit message cannot be reworded without a force push (which these standards forbid), so it must be caught before the push.
Enforcement
Documentation alone will not hold — the two PRs above were authored by people who knew about the trap and still shipped it. The team already runs a global ~/.git-hooks/pre-push. Add the grep above to it: fail the push when a close-keyword sits adjacent to an issue reference in any commit message about to be pushed, with an override for the intentional case.
This is the only point where a commit message is still cheap to change.
Acceptance criteria
-
standards/CLAUDE.mddocuments the negation trap, both vectors, the safe phrasings, and both checks. - The existing rule is amended so it no longer documents only the closing case.
-
~/.git-hooks/pre-push(or the hook the team syncs) scans outgoing commit messages for keyword-adjacent issue refs and blocks with a clear message naming the offending commit and line. - The hook has a documented override for a deliberate
Closes #Nin a commit message. - Verified by attempting a push of a commit whose message reads
does not close #1and confirming it is blocked.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Branch Naming and PR Linking section in standards/CLAUDE.md and inspect the existing ~/.git-hooks/pre-push or synced hook. Review how outgoing commit messages are collected and how intentional exceptions can be represented without weakening the check. Done means the documentation covers both vectors and the hook blocks a test message such as “does not close #1” while documenting an override for deliberate closing messages.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, github, shell
- Domain
- documentation, tooling
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100