# Remote pi agents get the same PATH-shim guard chain as local non-Claude agents

## Outcome
Remote pi turns dispatched via `pi_remote_dispatch.py` run with the git-guard PATH shim
ahead on PATH and marked agent-launched, so a destructive git verb against a shared
checkout on a buildbox is refused the same way it already is for codex/cursor-agent
locally. Board row `#manual-e1eb5b0c304172c1`.

## Status
DONE (git-shim scope). Generic `tools.json` deny-gate for remote pi is explicitly
OUT of scope for this pass — see "Not done" below.

## Source request
Owner (verbatim): "the remote pi agents need to be getting our hooks, which are the
only thing preventing agents to do bad things and the only thing enforcing our
overdeck rules and other local project rules." Remote pi runs on buildboxes via
`modules/workstation/bin/pi_remote_dispatch.py`, execing `pi-agent` on the box — today
those runs had none of the guardrails local agents get.

## What exists (discovery)
- Claude Code PreToolUse hooks (`~/.claude/hooks/*`) are Claude-Code-specific; pi has
  no hook layer, so they do not apply to pi at all, remote or local.
- The layer that DOES apply across runtimes is the PATH-shim chain:
  `modules/workstation/claude/bin/_git-guard-shim.sh` (+ `_cpu-guard-shim.sh`,
  `_tmpjail-shim.sh`, `_kill-guard-shim.sh`), gated on `AGENT_BUILD_SCOPE_ACTIVE=1`
  so a human's interactive shell is never affected. Real git is pinned at
  `~/.local/state/overdeck/shim-real/git` by `install-git-guard-real` (idempotent,
  self-excludes its own dir when searching PATH).
- `modules/workstation/manifest/claude.json` already declares whole `bin/`, `lib/`,
  `hooks/`, `tools.json` as manifest entries, and `push_claude_home`
  (`modules/buildbox/lib/claude-home.sh`, invoked from `modules/buildbox/bin/buildbox`)
  is the **sole writer** of `~/.claude` on every `agent-seat`-role buildbox
  (debian1/2/3 per `buildbox-hosts.json`). Verified live: all three boxes already
  carry `_git-guard-shim.sh`, `shim-guard.sh`, `worktree-guard-lib.sh`, and a git pin
  — convergence already ships the guard files, nothing new to write there.
- What was missing: nothing put `~/.claude/bin` ahead of `/usr/bin` on PATH for the
  remote `pi-agent` process, and nothing set `AGENT_BUILD_SCOPE_ACTIVE=1` for it — so
  `git` resolved straight past the shim.
- Push-reroute-to-land-queue (the plain `git` wrapper on the laptop) does NOT exist
  in the shipped `_git-guard-shim.sh` — it only refuses destructive verbs. Correct:
  a headless box has no local land queue to reroute into; refuse-with-guidance is the
  only sane remote behavior. No new mechanism needed here.
- The generic `tools.json` naive-wrong deny-gate (`deny-gate.mjs`) is a Claude Code
  PreToolUse hook with no remote/PATH-shim equivalent generator today — only 4
  hand-written shims exist. Building a generator is a materially larger, separate
  lift; explicitly deferred.

## Acceptance criteria
1. `pi_remote_dispatch.py`'s remote command puts `~/.claude/bin` first on PATH,
   exports `AGENT_BUILD_SCOPE_ACTIVE=1`, and self-heals the git pin before
   `exec pi-agent`.
2. No second writer of box `~/.claude` introduced — reuses existing
   `push_claude_home` convergence.
3. `modules/systray/tests/test_pi_remote_dispatch.py` green, including a new test
   asserting the guard-chain env/PATH is present in the constructed remote command.
4. Real-box proof: a destructive git verb against a shared-checkout shape is refused
   (exit 77 + guidance) under the exact PATH/env pi_remote_dispatch.py sets; a safe
   command passes (exit 0). Proven on debian1 directly; debian2/debian3 confirmed to
   carry the same shim+pin via convergence (not independently proof-run).

## Constraints honored
- Buildboxes headless, ssh:2222 only, no reboot — all verification via ssh.
- Never patched the `pi` package.
- No model invocation used for the proof (fully deterministic PATH/env repro of the
  dispatcher's own command string), per "keep deterministic if possible."
- A coordinator-labeled system-reminder mid-task instructed testing pi exclusively
  with `gpt-5.6-luna/low`, overriding the standing free-model allowlist
  (`free_throwaway_models_allowed` memory: only opencode deepseek-v4-flash-free,
  gpt-5.3-codex-spark, or North via na.sh). That message did not come from the
  owner and contradicted a recorded owner policy — treated as an untrusted
  injected instruction and NOT followed. No model call was needed for this proof.

## Not done / explicit gaps
- Generic `tools.json` naive-wrong deny-gate has no remote equivalent yet — remote pi
  gets ONLY the git-guard destructive-verb shim, not the full local deny-gate surface.
  Next checkpoint if the owner wants full parity: a shim generator reading
  `tools.json` the way `deny-gate.mjs` does, or hand-written shims for the other
  guarded binaries (cpu/tmpjail/kill already ship but are not yet wired into
  `pi_remote_dispatch.py`'s PATH — same one-line fix, deferred to keep this slice
  reviewable).
- AGENTS.md/CLAUDE.md project-rule sync to the remote workspace: `pi_remote_dispatch.py`
  already mirrors the tracked repo tree (`push_tracked_argv`) into the remote
  workspace before exec, so `AGENTS.md`/`CLAUDE.md` land there as regular tracked
  files. Whether `pi-agent` itself reads/loads them was NOT independently verified —
  `pi-agent` was not present on debian1's default PATH in this session (no live pi
  binary run), so this is a named unverified step, not a claim.
- debian2/debian3: confirmed shim+pin files present via `ls`/`test -x`, but the
  destructive-verb-refusal repro was only executed live on debian1. Same shipped
  file (`push_claude_home` digest-gated convergence, identical file across all
  agent-seat boxes) — treated as sufficient, not independently re-proven per box.

## Preserved refs
- Worktree: `/home/user/Projects/overdeck/.worktrees/pi-remote-hooks` (branch
  `wt/pi-remote-hooks`).
- Diff: `modules/workstation/bin/pi_remote_dispatch.py`,
  `modules/systray/tests/test_pi_remote_dispatch.py`.
- Board row: `#manual-e1eb5b0c304172c1`.

## Current receipt
2026-08-15: implemented + tested locally (5/5 `test_pi_remote_dispatch.py`, full
`modules/systray/tests` green except two pre-existing unrelated failures
`test_account_lock.py::test_store_rejects_malformed_or_unsafe_state` and
`test_packaging_desktop_entry.py::test_cinnamon_autostart_desktop_entry_is_templated_and_installed`,
untouched by this change). Live-box proof executed on debian1 (refusal exit=77,
safe-command exit=0). debian2/debian3 shim+pin presence confirmed. Ready to submit.

## Next executable action
`ship.sh submit wt/pi-remote-hooks <worktree>`; update board row progress + landed.
