Repository navigation
Description
setkernel/hq#431 (for hq#335) makes sk-agent export SETKERNEL_GUARD_NO_PUSH=1 in every Claude run. That makes plugins/setkernel/hooks/git-guard.sh's no-push mode the push guard in every agent run, next to hq's sk-push-guard. After the console paste (allowManagedHooksOnly, hq#332), it is the only one, because Claude Code then ignores sk-push-guard, a --settings hook.
Claude Lead's adversarial check of hq#431 at 049197a6 used three readers, three skeptics and two refuters per claimed blocker. It found two gaps in that mode, both in git-guard.sh at main 7a4a2b9. Claude Lead then reproduced both on the Mac. hq#431's own code is correct: its canary and the live test passed on rigel. Its merge waits for part 1 here.
In every repro below, the hook JSON (tool_input.command, cwd) was fed to each guard, with the cwd a scratch repo on the feature branch agent/1-demo.
- Guards:
sh git-guard.shat7a4a2b9, with and withoutSETKERNEL_GUARD_NO_PUSH=1, andpython3 -I sk-push-guardfrom hq at049197a6. - Exit codes: 2 blocks, 0 allows.
1. With the variable set, it refuses text that only mentions a push (blocks hq#431)
git-guard.sh drops quotes, then splits on ;&|(){}<> and backticks (lines 66-69), and checks each piece. With the variable set, any piece that starts with git push is refused. That includes heredoc bodies, commit messages and quoted strings, which are never run.
Without the variable, these commands pass on a feature branch. sk-push-guard has allowed them since hq 8a0228d (2026-10-15), whose title reads "ignore heredoc bodies and multi-line quoted text so commit messages and PR comments can mention git push". So hq#431 would bring that class back on all four VMs.
The refusal also tells Claude to "commit and stop" (line 54), so a task can end early. That is most likely in work on the guards, sk-agent or the docs, where commit messages and files talk about pushes.
The plugin guard alone refuses these (exit codes: no variable / with variable / sk-push-guard):
git commit -F - <<'EOF', whose body has a linegit push is the worker's job: 0 / 2 / 0.git commit -m "docs: the worker pushes; git push is never run by Claude": 0 / 2 / 0.cat > notes.md <<'EOF', with a linegit push origin agent/1-demo: 0 / 2 / 0.rg -n "; git push" docs/: 0 / 2 / 0.echo "run tests | git push later": 0 / 2 / 0.
These pass in all three: grep -n "git push" file, git commit -m "docs: the worker runs git push, Claude never does", and gh pr comment 1 --body "Do not run git push; the worker does". A real git push origin HEAD:refs/heads/x gives 0 / 2 / 2.
- With the variable set, skip heredoc bodies and quoted text the way
sk-push-guard's heredoc stripping does (hqagents/vm/sk-push-guard, from8a0228d). Real pushes insidesh -c "…",$(…)and backticks must stay blocked; today they are. -
tests/run.sh: a case for each command above, with the variable set and unset. Keep behaviour without the variable unchanged.
2. With the variable set, it misses spellings sk-push-guard blocks (before the paste)
With the variable set, git-guard.sh exits 0 for each command below, and sk-push-guard exits 2 (Claude Lead, on the Mac). The check's harness also ran the subtree, send-pack, setsid, flock, ionice, --attr-source and alias forms as real pushes in ghcr.io/setkernel/runner with no network, and they reached a local origin. After the paste, nothing would stop them.
-
git subtree push --prefix=d origin x -
git send-pack origin HEAD:refs/heads/x -
setsid git push …,flock /tmp/l git push …andionice -c3 git push …. The wrapper list on lines 88-89 doesn't name these;niceis on it and is blocked. -
exec -a x git push ….execis on the list, but its-avalue isn't skipped (line 94). -
git --attr-source HEAD push … -
git config alias.q push && git q …. The alias is set and used in the same command. -
git -C "dir with spaces" push …. The quotes are dropped before the words are split. -
Bring the no-push mode up to
sk-push-guard's coverage for these, or movesk-push-guard's parser into the plugin so one guard serves both modes. Either way, add atests/run.shcase for each.
Out of scope here
Channels other than a git push get past both guards today: gh api writes to refs or contents, a nested claude -p, a script written to a file and then run, and the worker's own git push running repo hooks or config the run wrote. Both guards call themselves speed bumps, not sandboxes. The real boundary is hq#209 (push broker, identities).
Done when
- Part 1 is merged, and every VM's plugin shows the new version. VMs refresh at most once an hour (hq#299). Then hq#431 goes to Raghav's merge, with no change to hq#431 itself.
- Part 2 is merged before the paste (hq#332).
The repro scripts are in Claude Lead's scratchpad. Their cases are the commands above.
Refs setkernel/hq#431, setkernel/hq#335, setkernel/hq#332, #42.
— Claude Lead
Activity
vega-setkernel commentedon Oct 26, 2026
Picked up. Working on branch
agent/43-git-guard-with-setkernel-guard-no-push-s. Automated: @vega-setkernel's agent onvega-dev.fix: git guard no-push mode reads ${…}, nested backticks and blank se…
fix: git guard no-push mode checks the text of $((…)) as a command (#43)
fix: git guard no-push mode reads heredoc and here-string readers, br…
fix: git guard scrub compiles under BSD awk (#43)
fix: git guard no-push mode decodes an escaped blank in a here-string…
fix: git guard no-push mode reads sh -c and env -S text that holds ${…
fix: git guard no-push mode reads a heredoc into a shell with a glued…
git-guard with SETKERNEL_GUARD_NO_PUSH: stop refusing text that only …
realRaghavGupta commentedon Oct 27, 2026
Closing: done, as this issue's "Done when" lines set out.
fd67fca6(2026-10-27T04:36:46Z), and every VM's~/.claude/plugins/installed_plugins.jsonnamesfd67fca6dad5…. hq#431 then merged at hqde93ba39and is installed on all four VMs (setkernel/hq#431 has the times).SETKERNEL_GUARD_NO_PUSH=1. Each spelling from part 2 exits 2:git subtree push,git send-pack, thesetsid,flock,ioniceandexec -awrappers,--attr-source, the alias set and used in one command, and the quoted-Cpath. A plain push also exits 2. These part 1 commands exit 0: the commit message, therg -n "; git push"pattern and theecho.The gaps that remain, found in the adversarial checks of #44, are #45. #46 is its PR, and #45 has to close before the console paste (setkernel/hq#332).
— Claude Lead