harness: hooks.json fix + git-add guard + sandbox git-config docs (v0.1.2) - #60
Merged
Merged
Conversation
… blanket git add The strict sandbox bind-mounts /dev/null over .git/config.lock and denies .git/config + .git/hooks writes as a built-in git ACE protection. Verified 2026-07-07 that sandbox.filesystem.allowWrite cannot lift it (mount layer vs permission layer), and excludedCommands:[git] is rejected as a full-ACE escape -> the correct workflow is to run git config/hook writes in a real terminal. Commit/push/worktree are unaffected (they write refs/index/objects, allowed). The recurring 'agent can't commit' symptom is git add -A / commit -a choking on masked /dev/null device nodes in the worktree. The subagent prompts already forbid this; the top-level agent (governed by CLAUDE.md) did not. Add the rule to both this repo's CLAUDE.md and the setup template so new downstream repos ship with it. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01NTZEsfFT4mrZpv8CY67Frd
Ships the git-add staging rule in the CLAUDE.md setup template so downstream repos pick it up on the next /orchestrator:setup. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01NTZEsfFT4mrZpv8CY67Frd
…en sandbox masks present CLAUDE.md is user-owned and never synced, so a text rule there can't reach downstream consumers. Enforce it at the harness level instead: a plugin-owned PreToolUse Bash hook (scripts/guard-git-add.py) blocks 'git add -A/./--all' and 'git commit -a/-am' and points the agent at explicit staging. The guard fires only when the worktree actually contains /dev/null device-node masks, so it is a no-op outside the sandbox and never obstructs consumers who don't run hardened. Mask detection uses os.stat (S_ISCHR), not 'find -type c': the bind-mount is invisible to readdir d_type, so find misses it. Ships with the plugin, so a version bump delivers it to every downstream repo and to the top-level agent, not just subagents. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01NTZEsfFT4mrZpv8CY67Frd
…etection The PreToolUse hook runs OUTSIDE the sandbox, so os.stat cannot see the /dev/null device-node masks (they exist only inside the per-command bwrap namespace) — the original mask-detection gate never fired. Detect the cause instead: read sandbox.enabled from settings.local.json/settings.json/user settings. Verified live: blanket 'git add -A' is now blocked with guidance, while git status / git add -u / explicit paths pass through untouched. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01NTZEsfFT4mrZpv8CY67Frd
robercano
approved these changes
Jul 7, 2026
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.
Summary
Ships the plugin
hooks.jsonfix and bumps the orchestrator plugin to 0.1.2, and adds aharness-level guard plus documentation around a sandbox git-config gotcha discovered along the way.
What's included
1. Plugin hooks fix + version bump (0.1.0 → 0.1.2)
hooks.jsonevents under a top-level"hooks"key so the plugin's hooks actually load.plugin.json+marketplace.jsonto0.1.2so downstream repos pick up the fixes.2. Harness guard: block blanket
git add -A/git commit -aPreToolUseBash hook (.claude/scripts/guard-git-add.py) that stops blanketgit add -A/./--allandgit commit -a/-amand points the agent at explicit staging./dev/null(crw-devicenodes); git can't index a device node, so a blanket add aborts the whole commit. Subagent prompts
already forbade this, but the top-level agent (governed by user-owned
CLAUDE.md) did not — andCLAUDE.mdis never synced, so a text rule there can't reach consumers. A plugin-owned hook does.sandbox.enabledis set (a no-op for non-hardened repos). Note it runsoutside the sandbox, so it can't see the masks directly (
os.statreports them absent) — it keys offsettings instead. Verified live: blanket adds blocked with guidance;
git status/git add -u/explicit paths pass through.
CLAUDE.mdand the setup template so new repos scaffold with it.3. Docs: the git-config sandbox mask is unfixable in-sandbox
docs/HARDENING.md: the strict sandbox masks.git/config.lockas/dev/nulland denies.git/config+.git/hooks/writes (a built-in git ACE protection). Verified thatsandbox.filesystem.allowWritecannot lift it (mount layer vs permission layer), andexcludedCommands: ["git"]is rejected as a full-ACE escape. Correct workflow: run git config/hookwrites in a real terminal.
git commit/push/worktreeare unaffected.Testing
--amend,-u, explicit paths, non-git pass).allowWritenon-fix and the git-config mask behavior verified empirically (process-start vssettings-mtime confirmed the restart actually took effect).
🤖 Generated with Claude Code