fix(hook-kit): reconcile the three diverged bash-guard copies into one union - #394
Open
DrumRobot wants to merge 1 commit into
Open
fix(hook-kit): reconcile the three diverged bash-guard copies into one union#394DrumRobot wants to merge 1 commit into
DrumRobot wants to merge 1 commit into
Conversation
…e union
bash-guard.py existed as three independently edited copies, and each carried
guard logic the others lacked. Past fixes were applied to whichever copy was
executing at the time and never travelled back to git, so the tracked file was
missing six functions that were actively guarding real sessions.
Divergence map (verified, not inferred):
tracked (next-fix) 11 fns staging gate, no force-push fn, 93 self-tests
plugin cache (es6kr) 13 fns heredoc stripping, worktree-add exemption,
pr-merge-ready check, transcript-aware
pr-create-draft; no staging gate, 99 tests
~/.agents/skills copy 14 fns conditional force-push guard + 2 helpers,
93 tests (no coverage for its own guard)
This commit takes the union. The cache copy is the skeleton because its two
overlapping functions are strict supersets of the tracked ones; the staging
gate comes from the tracked copy, and the force-push trio from the third.
Behaviour change worth calling out: force-pushing stops being an unconditional
block. Targeting a protected branch is still denied, but a lease-checked push
to a feature branch is now allowed, matching the conditional-allow fix that
until now lived only in the untracked copy.
Verified: 105 self-test cases pass (93 + 6 cache-only + 6 staging), and a
behaviour matrix over force-pushing, the staging base gate, hard reset and
pr-create-draft returns the intended verdict for each. Repo lint, verify-spec
and pytest all pass.
Demonstrated while committing this: the task-plugin cache copy lacks the
heredoc stripping the es6kr copy has, so it denied this very commit by matching
a phrase inside the message body. That false positive is the drift's cost.
Not addressed here: the copies still drift, because the runtime loads from the
plugin cache while git tracks the marketplace source, and both `es6kr` and
`task` cache the same hook - so a sync target is two directories, not one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EQM3BG4bwWxdhqM8cPDUw
DrumRobot
changed the base branch from
next-fix
to
fix/rag-dispatch-config-resolved
August 29, 2026 00:25
DrumRobot
changed the base branch from
fix/rag-dispatch-config-resolved
to
next-fix
August 29, 2026 00:26
DrumRobot
marked this pull request as ready for review
August 29, 2026 01:00
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.
Problem
bash-guard.pywas living as three independently edited copies. Fixes landed on whichever copy happened to be executing, and never travelled back to git — so the tracked file was missing six functions that were actively guarding real sessions.es6kr)~/.agents/skillscopyThe runtime loads from the plugin cache, not the marketplace — confirmed from
installed_plugins.json'sinstallPath, not inferred from error-message paths. Two plugins (es6kr,task) cache the same hook, so both run, and they disagree.Change
Union merge. The cache copy is the skeleton because its two overlapping functions are strict supersets of the tracked ones; the staging gate comes from the tracked copy and the force-push trio from the third. Three module constants and one transitive dependency came along with them.
Behaviour change worth review: force-pushing stops being an unconditional block. Targeting a protected branch is still denied; a lease-checked push to a feature branch is now allowed. That conditional-allow fix existed only in the untracked copy until now.
Test plan
--testself-suite: 105 cases pass (93 tracked + 6 cache-only + 6 staging)python3 -m py_compilemake lint,make verify-spec,make test(9 passed)Known gap (not in this PR)
The copies will drift again: git tracks the marketplace source while the runtime reads the cache, and the sync target is two cache directories rather than one. A structural fix (symlink, or a copy-comparison step in the hook audit) is a separate change. The other staging branch already carries
feat(hook-kit): check the runtime copies a hook actually has, which looks like the start of that work.While committing this, the
taskcache copy — which lacks the heredoc stripping thees6krcopy has — denied the commit by matching a phrase inside the message body. That false positive is the drift's cost, observed rather than hypothesised.