Claude was following a hotfix procedure that could put develop work into production. The procedure was an April copy of the hotfix steps inside /item, and the canonical ai/shared/workflow had moved twelve times in August.

Our first story was wrong

We first read it as an agent-memory problem. That diagnosis made Claude the moving part and let the documents look settled. It was the wrong frame. The command had a procedure near at hand, and Claude had followed it.

The copy won because it was near the command

The documentation already named an authority order. Root guidance came first, then ai/shared/workflow, then everything else. The canonical file owned Git flow, including the branch model, sync, save, release, and hotfixes. It was maintained as the place where those rules changed.

The /item skill had its own hotfix release steps anyway. A skill that embeds a procedure becomes another copy of it, with its own maintenance burden. We had treated proximity to the command as clarity. In practice, two files were competing to define the same release path.

That mattered because the hotfix path is narrow. A hotfix branch is cut from master, merged back to master, then carried from master to develop. The workflow blocks a hotfix branch that contains a commit reachable from develop but absent from master. If a fix actually needs in-progress code, it belongs on the staging path.

The branch check was reading the wrong refs

The stale procedure was only the visible problem. During the investigation, the hotfix branch was re-cut from master to match the documented workflow. Then a more serious defect appeared in ai/scripts/verify-hotfix-branch.sh. The guard was reading stale local refs instead of origin refs, so it could silently allow the exact develop-cut contamination it existed to stop.

That changed the incident. The copied instructions could send Claude down the wrong path. The ancestry guard could also bless that path using an out-of-date view of the repository. We had trusted that a verification step existed without asking what evidence it was reading. A check that compares the wrong refs gives a green result against an old snapshot, and the result looks deliberate.

The canonical workflow had already made the invariant plain. The branch, not the ticket label, decides whether work is a hotfix or staging. A branch cut from develop is unsafe for the hotfix release flow no matter what anyone calls it. The code had a guard for that condition, and the guard had been looking in the wrong place.

The first CI guard was too weak

Deleting the embedded steps and pointing /item at canon addressed the ownership failure. The first CI guard for that rule did not hold. It was too weak and would have shipped undetected. The next commit made the rule stricter and added seven cases covering false positives. Both runs were green in CI.

Those seven cases matter because a guard against copied procedures turns into noise if it sees duplication where there is none. They put a boundary around the rule, which now covers one thing: inlining a release procedure that already has an owner.

The completed change removed the April copy, made the skill cite the canonical workflow, and made CI reject another inline procedure. A change to the hotfix flow now lands in the one file the skill cites, and nobody has to remember that /item carries a private transcription. The branch-ancestry guard was corrected to read origin refs, and the change shipped in a release.

When an agent follows a procedure, find where that procedure lives and whether a second copy exists. Then check what each safety check actually reads.