Skip to content

[BUG] the commit-msg AI-attribution patterns match any word containing "ai", so they refuse legitimate commits - and a guard that refuses correct work is the guard that gets disabled #16

Description

@oberstet

What happened

.ai/.githooks/commit-msg rejected this line in an ordinary commit message:

The audit file: `.audit/<user>_<branch>.md`, generated by `.ai/justfile` and
committed as the first commit of every dev branch by `just new-branch`.

with:

ERROR: Commit rejected - violates the project's AI_POLICY.md
The commit message contains AI authorship attribution which is prohibited.

There is no authorship attribution in that sentence. It describes which tool
produces a file.

Why

grep -i -E "(authored by|co-authored by|generated by).*(claude|gemini|copilot|ai|artificial intelligence)"

The ai alternative is a bare, unanchored substring. After generated by, the
.* will reach any later ai anywhere in the message - including inside an
ordinary word. All of these are rejected today:

message fragment why it matches
generated by `.ai/justfile` the literal path
generated by the maintainer maintainer
generated by an available script available
generated by the certificate chain chain
generated by the CI pipeline, details above details

Pattern 3 has the same defect: (generated with|created with|built with|made with).*(claude|gemini|copilot|ai).

Pattern 2 is fine - it requires a trailer key first, and the alternation is
applied to a line already matched as ...-by:.

Why this matters more than a nuisance

A guard that refuses correct work is the guard that gets switched off.

That is not a hypothesis here. aaiare-metal-typedefint#58 records a clone of
system-and-software-assurance found running with
core.hooksPath = /dev/null - the exact value this hook's own error message
tells a human to set - with the re-arming step never performed. Every commit-msg
check was inactive there for months, on the machine that holds the credentials.

Nobody knows why it was disabled that time. But a hook that rejects
generated by the maintainer supplies the motive, and this is the second time
today it has refused a legitimate commit from an ordinary sentence.

The estate's own axioms name this: a check that cannot pass will be muted.
False positives are the slow version - the check can pass, but only after the
author rewrites a true sentence to appease a regex, and eventually someone
reaches for /dev/null instead.

Suggested fix

Require a word boundary and drop the bare two-letter alternative in the prose
patterns:

# was: (claude|gemini|copilot|ai|artificial intelligence)
# now:
"(authored by|co-authored by|generated by)[[:space:]]+(claude|gemini|copilot|chatgpt|an? ai\b|artificial intelligence)"

Two changes, and both matter:

  1. [[:space:]]+ instead of .* - attribution is adjacent to the phrase.
    .* lets the phrase match something twenty lines away that was never part of
    the same claim.
  2. an? ai\b instead of ai - "by an AI" and "by AI" are attribution;
    "maintainer" is not.

Acceptance criteria

  1. Observed refusing, output pasted, for each of: Co-Authored-By: Claude <...>, Generated with Claude Code, authored by an AI.
  2. Observed ACCEPTING, output pasted, for each of: generated by `.ai/justfile` , generated by the maintainer, built with available tooling - and a message ending in the sanctioned
    Note: This work was completed with AI assistance (Claude Code).
  3. A note in the hook explaining why the alternation is anchored, so the bare
    ai is not reintroduced by someone broadening the check in good faith.

Note on blast radius

This hook is shared by every repository using .ai, so the fix propagates on
the next submodule update - which is the reason to keep the change small and its
negative cases explicit. The second acceptance criterion is the one that matters:
a stricter guard is easy, and this one is already too strict.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions