Description
Extend guard's approval gate to also exempt writes outside the project directory,
alongside the existing git-ignored exemption. A write whose resolved target is not
under CLAUDE_PROJECT_DIR (e.g. the session scratchpad under /private/tmp, or
~/.claude/...) is not the user's tracked project source, and Bash can already write
there ungated — so gating the file-editing tools for it was pure friction.
Changes (plugins/guard/scripts/guard_hook.py):
- New
_is_outside_project(project_dir, target) — true when the resolved target is
not the project dir and not under it; fail-toward-inside (keep gating) if the project
dir can't be resolved.
cmd_gate now allows an unapproved write when the target is outside the project dir
(trace allow_outside_repo), checked before the git check-ignore exemption (and it
skips that subprocess for out-of-repo paths).
- Guard's own config + state stay excluded via
_is_guard_owned; they live in-repo, so
the out-of-repo exemption never applies to them.
- Version 0.3.0 → 0.3.1; docs updated (
AGENTS.md, dev/design.md, README.md).
Context
Follow-up to #172 / #174. Surfaced by dogfooding: with guard active in-session, a Write
to the session scratchpad (/private/tmp/.../scratchpad) was denied because
git check-ignore can't classify an out-of-repo path, so it wasn't covered by the
git-ignore exemption. The gate's purpose is to guard the user's tracked project source;
out-of-repo paths fall outside that scope entirely.
Acceptance Criteria
- Direct gate matrix against the real repo (unapproved) — verified:
- out-of-repo →
allow_outside_repo: the session scratchpad under /private/tmp,
/tmp/whatever.txt, ~/.claude/settings.json.
- tracked source (
guard_hook.py, AGENTS.md) → DENY.
- in-repo git-ignored (
_x.local.md) → allow_gitignored.
- guard-owned in-repo (
.claude/guard/state/evil.json, .claude/guard.local.json)
→ DENY (out-of-repo exemption does not weaken self-arm / self-disable protection).
python3 -c "import ast; ast.parse(...)" passes.
Description
Extend guard's approval gate to also exempt writes outside the project directory,
alongside the existing git-ignored exemption. A write whose resolved target is not
under
CLAUDE_PROJECT_DIR(e.g. the session scratchpad under/private/tmp, or~/.claude/...) is not the user's tracked project source, and Bash can already writethere ungated — so gating the file-editing tools for it was pure friction.
Changes (
plugins/guard/scripts/guard_hook.py):_is_outside_project(project_dir, target)— true when the resolved target isnot the project dir and not under it; fail-toward-inside (keep gating) if the project
dir can't be resolved.
cmd_gatenow allows an unapproved write when the target is outside the project dir(trace
allow_outside_repo), checked before thegit check-ignoreexemption (and itskips that subprocess for out-of-repo paths).
_is_guard_owned; they live in-repo, sothe out-of-repo exemption never applies to them.
AGENTS.md,dev/design.md,README.md).Context
Follow-up to #172 / #174. Surfaced by dogfooding: with guard active in-session, a Write
to the session scratchpad (
/private/tmp/.../scratchpad) was denied becausegit check-ignorecan't classify an out-of-repo path, so it wasn't covered by thegit-ignore exemption. The gate's purpose is to guard the user's tracked project source;
out-of-repo paths fall outside that scope entirely.
Acceptance Criteria
allow_outside_repo: the session scratchpad under/private/tmp,/tmp/whatever.txt,~/.claude/settings.json.guard_hook.py,AGENTS.md) → DENY._x.local.md) →allow_gitignored..claude/guard/state/evil.json,.claude/guard.local.json)→ DENY (out-of-repo exemption does not weaken self-arm / self-disable protection).
python3 -c "import ast; ast.parse(...)"passes.