# Agent shell guard

> Harn gives Codex and Claude the same repository command rules. The guard keeps Rust builds and tests on the Make targets that configure a private build directory for each...

Website: https://harnlang.com/dev/agent-shell-guard.html

This page documents Harn, which is pre-1.0. Language, standard library, and CLI APIs may change. If the intended version is unclear, clarify before using this page.

---

Harn gives Codex and Claude the same repository command rules. The guard keeps
Rust builds and tests on the Make targets that configure a private build
directory for each worktree. It also keeps Fleet worktree creation behind its
durable admission command and stops long commands from losing most of their
output in a filter.

The guard applies only to agent shell calls. It does not change commands run by
people, continuous integration, Make recipes, or repository scripts.

## Use the supported commands

Run the Make target when one owns the operation:

| Instead of | Run |
|---|---|
| `cargo build` | `make build` |
| `cargo check` | `make check` |
| `cargo test` or `cargo nextest` | `make test` |
| `cargo clippy` | `make lint` |
| `cargo fmt` | `make fmt` |
| `cargo bench` | `make bench` |

Inspection and package-management commands such as `cargo tree` and
`cargo add` remain available because no Make target owns them.

For a real one-off, add `HARN_ALLOW_RAW_CARGO=1` to that command. The escape is
visible in the shell call and does not weaken later calls.

## Admit Fleet worktrees before creating them

Agent shell calls cannot run raw `git worktree add`. Use the owning
`fleet-worktree-admit` command, which acquires the remote lease and records the
joined ledger and recovery receipts before it creates the worktree. The guard
does not expose an environment-variable bypass for this rule.

This restriction does not affect people, continuous integration, repository
scripts, `git worktree list`, or retirement through `git worktree remove`.

## Keep complete command output

Do not send a build or test directly into `grep`, `head`, or another filter.
Save the complete output first:

```bash
make test > .harn-runs/test.log 2>&1; result=$?
grep FAILED .harn-runs/test.log
exit "$result"
```

`tee` is also accepted when it names a log file:

```bash
make test 2>&1 | tee .harn-runs/test.log
```

This preserves the evidence needed to investigate a failure without repeating
the expensive command.

## Keep generated output out of Trash

Trash is reserved for user-authored or uncertain data that may need recovery.
Do not move temporary files, benchmark output, compiler caches, or other
reproducible build artifacts there: that turns cleanup into visible Trash
clutter. The guard rejects `trash` and `trash-put` when an argument resolves to
an OS temporary root or a repository-generated root such as `.build/`,
`target/`, `.harn-runs/`, or the Burin eval roots. It still allows Trash for
ordinary user files.

Prefer leaving a rebuildable cache in place. Put one-off output beneath an OS
temporary directory, and permanently remove it only through an exact,
validated path and a mechanism accepted by the host. If the host refuses that
cleanup, leave the data in its temporary/build root and report it instead of
using Trash as a fallback.

## Enable the hooks

The repository already carries both host adapters:

- Codex reads `.codex/hooks.json` after the project is trusted. Open `/hooks`
  to review the active hook. Codex asks for review again when the hook file
  changes.
- Claude Code reads `.claude/settings.json`. Open `/hooks` to inspect or disable
  the active hook.

Both adapters call `scripts/agent-shell-guard.sh`. That small adapter selects an
existing Harn executable and passes the host payload to
`scripts/agent_shell_guard.harn`, which owns every decision. The adapter never
starts a build. Its empty builtin allow list denies registered non-core
builtins and the typed Harness methods backed by them.
`HARN_LLM_CALLS_DISABLED=1` independently prevents a real model request.
Platform-specific temporary roots and generated-root configuration live in the
typed, data-only `scripts/agent_shell_guard_policy.harn` module; the evaluator
contains no second path list.

The adapter prefers a hook-owned release binary at
`$AGENT_SHELL_GUARD_HARN_BIN`, or the default Harn cache location, only when the
adjacent `.standalone-v1` file contains `harn-run-standalone-v1`. That runtime
uses `harn run --standalone`, so ambient handlers, packages, skills, and cache
authority cannot delay a command-policy decision. An explicit executable
`HARN_BIN` retains the project-aware invocation for compatibility with older
Harn releases. Without either path, the adapter falls back to an existing
repository or installed executable without building one.

Once that exact marker enrolls the cache, a checksum-verified `harn upgrade`
also refreshes the hook-owned binary from the downloaded release archive. The
updater serializes concurrent refreshes, publishes digest-keyed release and
source provenance, and atomically replaces the executable. An absent or
malformed marker never enrolls or mutates the cache. `--no-verify` and legacy
releases without a checksum manifest leave the existing hook runtime untouched.
If the upgrade command itself is running from that enrolled path, an unverified
self-upgrade is refused before either executable changes.
The hook itself never checks for updates, accesses the network, or hashes the
installed binary during command evaluation.

The project-aware fallback loads project trigger and hook handler code only if
a handler runs. A top-level handler initialization failure therefore cannot
disable command checks. Harn still parses `harn.toml` and validates handler
declarations.

The adapter bounds policy execution below the host hook deadline. It first
sends TERM, then KILL after a short grace so an interpreter descendant cannot
keep the hook pipe open. Exit statuses 124, 137, and 143 deny the command
because the policy timed out or was interrupted before proving it safe. A
missing interpreter or another runtime failure remains fail-open so a broken
local installation cannot lock the agent out of recovery. Policy output is
published only after a successful interpreter exit; partial output from a crash
or timeout is discarded instead of becoming a malformed host decision.

See the current [Codex hooks reference](https://developers.openai.com/codex/config-advanced#hooks)
and [Claude Code hooks reference](https://code.claude.com/docs/en/hooks) for the
host-level trust and configuration rules.

## Diagnose a decision

Send a host-shaped payload to the same adapter used by the agents:

```bash
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"cargo test"}}' \
  | scripts/agent-shell-guard.sh
```

A blocked command prints one JSON decision with the reason and replacement. An
allowed command prints nothing. To check the policy and its in-process tests:

```bash
harn check scripts/agent_shell_guard.harn
harn test scripts/tests/agent_shell_guard_test.harn
```

If the adapter stays silent unexpectedly, confirm that `harn` is installed or
set `HARN_BIN` to an existing executable. For the standalone path, also confirm
that the adjacent marker contains the exact attestation shown above. Then repeat
the probe with diagnostics enabled:

```bash
printf '%s' '{"tool_name":"Bash","tool_input":{"command":"cargo test"}}' \
  | AGENT_SHELL_GUARD_DEBUG=1 scripts/agent-shell-guard.sh
```

The adapter deliberately ignores an invalid executable path and remains
fail-open. Debug mode preserves Harn startup and policy errors on stderr.

## Reuse the policy in another repository

First give that repository Make targets with the same public names. Each target
must own its build directory and environment before the matching Cargo command
is blocked. Then project these files from Harn without editing their copies:

- `scripts/agent_shell_guard.harn`
- `scripts/agent_shell_guard_policy.harn`
- `scripts/agent-shell-guard.sh`

Add host settings that call the projected adapter. Keep repository-specific
startup hooks in the host settings rather than putting them in the shared
policy. The deferred handler startup matters most in these downstream projects,
where the command policy sits beside application triggers and hooks. The Harn
fleet manifest checks projected files byte for byte, so a policy change has one
owner and downstream drift becomes a failed check.

---

## Read next

- [Merge overrides](https://harnlang.com/dev/merge-overrides.md)
- [Deterministic test patterns](https://harnlang.com/dev/testing.md)
