fix(hook): stop refusing every Git command that follows another on one line - #37
Merged
Conversation
…e line One running flag was raised after every commit and every ref-affecting verb, then stamped onto every later candidate on the same shell line. The second Git command was refused whatever it was, and lines with no commit in them were still told to "run the Git commit separately". `git commit && git push`, `git fetch && git rebase`, `git remote -v && git push` and `git stash list && git commit` all denied. What matters is whether the earlier command moved something a later policy resolution reads: HEAD, the index, or the worktree `.aimhooman.json` comes from. That is now a third command set, raised only when the verb is not in a read-only listing form. It is a separate set rather than a narrowing of the two beside it, because those also decide whether a verb becomes a modelled candidate at all — dropping `push` from ref mutation would take the installed-guard check for `git push` with it. `commit` does not raise it. The state a commit moves is state this tool just supervised through pre-commit, commit-msg and the ref guard. `symbolic-ref`, `update-ref`, `rm --cached` and `stash pop` do raise it: they move HEAD or the index unwatched, and a commit after them resolves against state nothing checked. `stash` is in the set for that reason, though it is absent from the transition list. The alias resolver re-stamped the same flag after the parser had run, so the narrowing is applied in both places or it takes effect in neither. The read-only listing carve-out moved above the transition veto. A command that only reads cannot be made unsafe by whatever ran before it. It stays below the checks that fire when the repository cannot be seen at all. `git branch --show-current` now counts as a listing form. Tests: a second Git command is allowed when the first moved no policy input, and denied when it did, with `symbolic-ref`, `update-ref`, `rm --cached`, `stash pop` and `checkout` as the guardrails. The listing carve-out is now exercised under strict as well as clean — it only ran on clean, which is how a change that turned it off under strict could have shipped green.
rmyndharis
force-pushed
the
fix/guard-over-refusal
branch
from
August 12, 2026 10:04
0c9db7e to
d79d7a6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
policyTransitionRiskis one running boolean. It was raised unconditionallyafter every commit candidate and after every policy-transition or ref-mutation
verb, then stamped onto every later candidate on the same shell line and vetoed
before the candidate was examined.
So the second Git command on a line was refused whatever it was, and lines with
no commit in them were still told to "run the Git commit separately". Measured
against a freshly initialised repo on
clean:git commit -m wip && git push,git fetch && git rebase origin/main,git remote -v && git push,git tag -l && git push --tags,git stash list && git commit -m xandgit branch --show-current && git commit -m xall denied.What decides it now
Whether the earlier command moved something a later policy resolution reads:
HEAD, the index, or the worktree that
.aimhooman.jsoncomes from.That is a new third set,
GIT_POLICY_INPUT_COMMANDS, and the flag is raised onlywhen the verb is not in a read-only listing form. It is deliberately a separate
set rather than a narrowing of the two beside it:
GIT_POLICY_TRANSITION_COMMANDSand
GIT_REF_MUTATION_COMMANDSalso decide whether a verb becomes a modelledcandidate at all, so dropping
pushfrom ref mutation would take theinstalled-guard check for
git pushwith it.commitis absent from the set. The state a commit moves is state this tool hasjust supervised through pre-commit, commit-msg and the reference-transaction
guard.
symbolic-ref,update-ref,rm --cachedandstash popare present:they move HEAD or the index unwatched, so a commit after them resolves against
state nothing checked.
stashis in the set for that reason even though it isabsent from the transition list, and
rmandupdate-refare there becauseresolveStagedPolicyreads both the staged index andheadPolicy.pullstays in the set, sogit pull --rebase && git pushstill denies. Pullgenuinely changes the worktree the next command resolves policy from; narrowing
cannot fix that case and pretending otherwise would be the wrong trade.
Three sites, not one
The alias resolver re-stamped the same flag after the parser had run, so the
narrowing had to be applied in
resolveGitAliasesas well. Applied in only oneplace, none of the commit cases change.
The read-only listing carve-out moved above the transition veto. A command that
only reads cannot be made unsafe by whatever ran before it. It stays below the
checks that fire when the repository cannot be seen at all —
GIT_INDEX_FILE,target environment assignments, and dynamic targets.
git branch --show-currentnow counts as a listing form; it was missing fromthe read-only flag set.
Tests
Two new cases, one for each direction: a second Git command is allowed when the
first moved no policy input, and denied when it did, with
symbolic-ref,update-ref,rm --cached,stash popandcheckoutas the guardrails.The read-only listing test is now exercised under
strictas well asclean.It only ever ran on
clean, which is how a change that turns the carve-out offunder
strictcould ship green — the strict path denies through a differentbranch (
uncertainShellwith no candidates), so it needs its own coverage.Full suite green,
npm run checkgreen.