The branch was cut as a HOTFIX, and two weeks later JIRA called the same ticket a staging task. Nobody had changed the type field on purpose. It drifted, and it sat that way because the only thing that ever compares a branch name to its ticket is a human glancing at a PR title.
How it surfaced
This was one ticket. We found it while reviewing an image manager rebuild, so it wasn’t a dedicated audit. We were looking at branch state for something else. The ticket had been reclassified from HOTFIX to a staging task, and the branch was never renamed or re-cut to match. Git doesn’t know what a HOTFIX is. JIRA doesn’t watch git. The two records stopped agreeing, and every commit, review comment and automated post to the team feed treated the mismatch as normal, because no check existed that would call it abnormal.
A HOTFIX branch usually implies a different review bar, a different deploy path, sometimes a different base. If the ticket type says one thing and the branch does another, code can ship through the wrong gate, and you only find out when someone reconciles the two by hand. That is what happened here, as a side finding rather than a caught error.
The ledger we were already building
Our first instinct was something big: a background job that reconciles every open branch against JIRA state, flags drift, maybe auto-corrects labels. We had just been through that debate on a related ticket, “flag pending change-scripts”, and we had acted on it. Codex’s first answer was an append-only event ledger with deploy-time inputs and a write path fix. Manus answered in the same shape: a redesigned ledger, immutable events, a maintained current-state projection, and a consistency check.
Then the twenty-minute triage came back. A one-line state query against the database settled what the ledger was being built to infer. Codex’s own follow-up said its earlier ledger recommendation was wrong given that result, and that the right move was to ship verification only, because the database state is the record of fact. Manus agreed on the same scope cut minutes later. What shipped was a direct database check against the live change-script folders, now covering all 18 automatically, in place of a hand-typed index that had been wrong in both directions since May.
That reversal is why the branch and ticket guard didn’t become a monitor either. A branch only has to be right at one moment, the moment it’s cut. If someone changes the ticket type mid-flight, that is a real decision someone should look at and re-title the branch for. A background daemon shouldn’t silently reconcile it or re-flag it on every commit.
What shipped
A hook runs the moment a branch naming a ticket is cut. It checks:
- the branch’s naming shape matches the ticket’s type
- the type hasn’t been moved off HOTFIX
- status is in CODING
- priority is Working
It warns and prints the fix. It does not block. A false positive on a naming convention shouldn’t be able to stop someone from shipping a real hotfix under time pressure. A missed warning costs a late discovery, which is annoying but recoverable. A false block during an actual incident costs more.
The hook lives in the repo, not in a per-developer installer. We run ten clones of one large legacy codebase (site through site10), each a separate working copy that a different session might be driving. A guard in someone’s global git hooks directory only fires for the person who ran the install script. A guard committed to the repo fires the same way no matter which clone or machine cut the branch. The per-clone branch audit we built the same day follows the same logic. It reconciles every clone against live JIRA and reports only what disagrees, rather than trusting any single clone’s local state.
What both fixes have in common
The change-scripts ticket and the branch guard share a shape. Something was kept as a hand-maintained side record, either a typed-out change-script index or a branch name nobody re-checks against its ticket. The record drifted because nothing compared it to the source of truth at the point where drift is cheap to catch. Both fixes put a check at the one moment the two should agree, backed by the system that holds the fact: the database for change-scripts, JIRA for ticket state.
Ledgers and monitors get proposed first because they feel thorough. We proposed two of them, and the twenty-minute triage showed a single point-in-time query would have caught the same problem. If you keep a hand-maintained record that has to agree with a system of record, put a warning-only check at the moment they should match, and run the query before you fund the monitor.