TimZander / TimZander/claude

standards: a negated close-keyword still auto-closes the issue — document both vectors and enforce in pre-push

Open
#186 0 comments 0 reactions 0 assignees View on GitHub

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> or Fixes #<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

  1. PR body — linked before merge, visible via closingIssuesReferences.
  2. 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. closingIssuesReferences cannot see this — it reports NONE while 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 #N registers 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 when squash_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.md documents 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 #N in a commit message.
  • Verified by attempting a push of a commit whose message reads does not close #1 and confirming it is blocked.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.