/docs/session-walkthrough

A session, start to finish

nd7 run claude is the whole setup. It starts Claude Code with a set of rules the macOS kernel applies to it and to every process it spawns, and with three extra flags that make Claude Code cooperate with those rules. Nothing in your Claude Code settings changes; the flags apply to that one session. This page follows a session from the first command to the first denial and back.

Start a session#

cd your-project
nd7 run claude

The directory you are in when you run this is the project: the one place the agent may write from the start. nd7 run works out its full path, together with the temp directory and your home directory (taken from the system's user database, not from the $HOME variable), and builds the session's rules from them.

Before starting anything it checks that nd7-exec is installed next to nd7 and is not writable by group or others, and refuses to start if it is not. Then it creates a session directory at ~/.nd7/sessions/<pid>/, holding record.json (the rules, so nd7 allow can amend them) and policy.sb (the profile every Bash command runs under), and prints one line:

nd7 run: session 41287 under nd7's policy; a sandbox the program applies itself is refused

That number is the session's process id. You need it only if several sandboxed sessions are running at once.

After that, Claude Code starts as usual and behaves as usual. The session directory is removed when nd7 run exits.

What nd7 run passes to Claude Code#

Three things, all as command-line flags on that one invocation, from claude_flags() in src/bin/nd7.rs:

A PreToolUse hook on the Bash tool. Passed with --settings, as inline JSON:

{
  "sandbox": { "enabled": false },
  "hooks": {
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [ { "type": "command", "command": "/path/to/nd7 hook-prefix" } ] }
    ]
  }
}

The hook rewrites every Bash command to run through nd7-exec, which is the only program allowed out of the sandbox and which applies the session's current rules before running anything. That is what makes nd7 allow take effect without a restart. The hook also answers with permissionDecision: allow, so Claude Code does not also prompt you about a command the sandbox has already decided.

Claude Code's own sandbox turned off. That is the "sandbox": { "enabled": false } above. The kernel refuses a second sandbox profile inside the first one anyway; turning it off means the refusal does not cost a broken tool call. A consequence, stated in ADR-0007: Claude Code's /sandbox settings have no effect under nd7, and its hostname-based network filtering is lost until nd7 has a proxy of its own.

One paragraph appended to the system prompt, with --append-system-prompt, so that a denial is read as nd7's rules and not as Claude Code's permission prompts:

This session runs under nd7's kernel sandbox. Bash commands are routed through nd7-exec by a hook and run under the session's policy: the project directory is writable, most of the rest of the filesystem is not, and only HTTPS egress is open. An 'operation not permitted' error is that policy, not Claude Code's permission rules. Do not try to route around it; tell the user what was denied. nd7 allow <path> widens the policy for Bash commands from the next call on; the Write, Edit and Read tools see only the fixed policy, so for those the user must restart under a wider one.

These three flags are added only when the program being run is called claude. Any other program runs under the same sandbox without them: nd7 run zsh works, and so does anything else.

What the session may do#

From the start, without any grants:

  • write: allowed — the project directory, the temp directory, ~/.claude, and Claude Code's scratch directories under /private/tmp/claude-*
  • read: allowed — everything except ~/.ssh, ~/.aws and ~/.nd7
  • network: allowed — HTTPS on port 443, and DNS. git over HTTPS works; over SSH it does not
  • run: allowed — any program, inside the same boundary

How the macOS sandbox works has the exact rules, rule by rule.

The first denial, and how to widen#

Sooner or later the agent runs a command that writes outside the project. The kernel refuses the operation and the command fails with Operation not permitted. Because of the system-prompt paragraph, Claude Code reports it as nd7's rules and tells you what it wanted.

From another terminal, while the session is still running:

nd7 allow ~/data
session 41287: writes under /Users/you/data allowed from the next command

The next Bash command Claude Code runs is already under the wider rules. There is no restart, no daemon and no signal: the grant is written into the session's policy.sb, and nd7-exec reads that file again for every command. To take it back:

nd7 deny ~/data

If several sandboxed sessions are running, nd7 allow says so and asks for --session <pid>, using the pid nd7 run printed. Grants belong to one session and are gone when it ends. Paths under ~/.nd7 can never be granted. Change the rules while the agent works covers the details, and What happens when a command is denied covers the cases a grant cannot fix.

One limit worth knowing before you hit it: Claude Code's Write and Edit tools run inside the claude process itself, not as Bash commands, so they see only the rules the session started with. Widening them means restarting the session under a wider project, for example with nd7 run claude --resume <id>.

Recording as well as sandboxing#

nd7 run sets up the sandbox. It does not register the recorder. To also keep an audit log of the session, add nd7 record as a hook in your Claude Code settings once; see Set up nd7 as a Claude Code hook for the snippet, and How the audit log works for what ends up on disk.

Exit status and other programs#

nd7 run exits with the program's own exit code, or 128 + signal if a signal killed it, the way a shell reports it. A usage error exits 2.

There is also nd7 run --profile <file> <program>, which applies a raw Seatbelt profile from a file, with PROJ, TMP and HOME available as profile parameters and with no session created. It exists for experiments, not for daily use: without a session there are no grants and no nd7-exec exit.

Updated . This page as Markdown · Source on GitHub.