Tagout

v3.1.0: a Windows pass, a gate that reads the whole command, and one home for opening a PR

Released 2026-09-16. One feature and nineteen fixes, most of them field reports from the two days after 3.0.0.

What we were trying to fix

Three themes, all of them found by running the kit rather than by reading it.

Windows kept losing. 3.0.1 had fixed the symlinks, and the rest of the checkout then failed in its own ways: the eval runner staged skills by link and started on nothing, decision-check.py could not find bash on PATH, check-shared-refs.py crashed printing a refusal to a non-UTF-8 console, and Git Bash rewrote a plan path into a drive letter before git cat-file ever saw it. Four separate reports, one shape: the kit assumed a POSIX host in places nobody had looked.

The write gate judged the wrong string. It honoured a GIT_GATE=off prefix from anywhere, including a sub-agent that had been handed no such authority, and it inspected only the first word of a command, so a raw git push hidden inside sh -c '...' or an eval argument walked straight past it. A gate that reads part of a command is a gate that reports the part it read.

A finished branch had no way to become a pull request. Only implement-issue could open one, and it opens one for an issue it is already building. A fix that debug-issue committed, or any branch finished by hand, had nothing to reach for.

What we decided

create-pr becomes the one home for opening a pull request (#635): push through the guard, link the issue, gate the title, and open the PR ready when the full test passes or draft when it does not. implement-issue keeps its own draft PR step, but the recipe both of them follow now lives in one file under skills/_shared/.

The write gate reads the command it is actually judging: a shell’s -c string and an eval argument are parsed rather than skipped, and GIT_GATE=off is honoured only from the main thread, which is the only place the person who set it is sitting.

The Windows fixes were taken one at a time rather than behind a compatibility layer. Every one of them turned out to be a place where a POSIX assumption had been written without being noticed, so the fix and the assumption are both visible in the diff.

What got cut

The code review dispatch inside implement-issue was allowed to keep writing to the working tree. Forbidding it outright would have cost more than it saves, so the risk is named in the skill and post-review-check.sh proves afterwards whether the reviewer wrote past its own report. Naming and detecting, rather than preventing, is the cheaper half of the same guarantee.

What bit us

run-all-tests.sh carries a guard that asserts it has not drifted from the CI workflow, and it compared only the first line of each step. Two different steps whose first lines happened to match passed the drift check while the rest of them disagreed. A comparison that is cheap enough to write in one line is usually cheap enough to be wrong in one line too, and a drift guard that can agree with a tree it has never fully read is a green tick standing in for a check.