---
title: A session, start to finish
description: What nd7 run claude starts, what it passes to Claude Code, what you see, and what to do at the first denial.
order: 25
section: Getting started
---

`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

```sh
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](https://github.com/nd7-dev/nd7-core/blob/main/src/bin/nd7.rs):

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

```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](https://github.com/nd7-dev/nd7-core/blob/main/docs/DECISIONS.md#adr-0007-sandbox-claude-code-with-one-seatbelt-floor-and-a-trampoline-and-refuse-every-other-profile): 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](/docs/sandbox) 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:

```sh
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:

```sh
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](/docs/allow-deny) covers the details, and [What happens when a command is denied](/docs/denials) 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](/docs/claude-code-hooks) for the snippet, and [How the audit log works](/docs/audit-log) 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.
