Choose a harness

Harness tool controls

Apply hooks, policy and approval to a harness session.

For: plugin authors.

Apply hooks, policy and approval to a harness session.

Hooks and approvals for the session's tools

Every tool call in a Claude harness session asks first. The session can use only the tools in harness.tools, and each call runs only when the job allows it:

  • Hooks see it as a pre_tool_call named <harness>.<tool>, such as claude-agent-sdk.Bash, with the tool's input as arguments. They allow it, deny it, change its input or ask a person, as for any tool call.
  • Tools in harness.requires_approval wait for a person's approval of each call, once the hooks have let it through:
agents/acme-coder.yaml
harness:
  tools: [Read, Glob, Grep, Bash]
  requires_approval: [Bash]          # someone approves every command

Default-deny. A call to a tool the agent does not list in harness.tools (or, when it lists none, that its harness does not list as its default tools:) is refused on the spot, whatever the session's own settings say: the agent reads WebFetch is not among the tools this agent declares. An agent with oz.ask_user in its tools: may also ask people questions from the session.

A call that no hook matches and that needs no approval runs at once. The others go to the job's workflow, through the edge, so a call can wait days for a person without the session losing its place. A refused call is not made: the agent reads why, as the SDK reports a denied tool.

Which harnesses ask before a shell command

A hook, and the tenant's command rules, see a shell command only when the harness asks before it runs it. The command rules, on by default on every node with nothing installed, send catastrophic commands such as rm -rf ~ to a person; they, and the guardrails plugin's command guard, read only those:

HarnessAsks before a shell commandNamed in hooks
Claude Agent SDK (harness-claude)Every Bash callclaude-agent-sdk.Bash, with the command in arguments.command
Claude Managed Agents (remote-claude-managed)Every bash callclaude-managed.bash
goose (approve mode) and OpenCode ("*": "ask")Every callgoose.execute, opencode.execute, with the agent's input and the client terminals' command, args and cwd
Codex (read-only mode)Only commands that need more than its read-only sandboxcodex.execute
Gemini CLIShell commands, but not its read-only toolsgemini.execute
A harness of your ownOnly with permissions: true, for every call<harness>.<tool>

A Claude session's calls of an imported plugin's MCP servers (its install's harness extras) are not the harness's own tools: each is asked of the job and decided as the plugin's function it names (tool policy). Claude's tools that read MCP servers' resources are refused in every session, whatever the agent declares.

So Codex's sandboxed commands, Gemini CLI's read-only tools and every call of a harness without permissions run without a hook or a command rule seeing them: those are checks, and the sandbox is what bounds such a session.

The Plugin improver, an agent that proposes reviewed, evidence-backed changes to your plugins, runs only on harnesses that ask before every call: the Claude Agent SDK (improver), OpenCode and goose (improver-opencode, improver-goose); its PLUGIN_IMPROVER_HARNESS setting picks one. Codex and Gemini CLI declare cannot_ask, so they run no routine fire and no shadow run, and the improver does not offer them. Its agents have a shell to run the target's own tests (Bash; on ACP, execute, and no fetch), so every session of theirs runs on sandbox tier 1, whoever started the job, and the tenant's command rules decide each command.

A node a tenant admin opted out of the tier runs them at tier 0, which the job's page and the changeset's card say ("the session ran at sandbox tier 0"): keep the improver off such nodes with its install's node selector.

Sessions run in a sandbox on nodes with sandbox tier 1: Linux with bubblewrap, or macOS. Hooks and approvals decide whether a call may run. The sandbox then limits its access: the session writes only to its work and state directories, sees nothing else on the node, and reaches only hosts allowed by the tenant, plugin and agent egress: rules.Give the agent only tools its task needs. The platform requires tier 1 by default if the agent lists Bash, Write or Edit, leaves the tool list empty (which includes them by default), has an ACP execute tool, or uses a harness the platform cannot check. This applies whoever starts the job. Without an eligible node, the job fails after 10 minutes and names the remedies: provide a tier-1 node or use a node's sandbox opt-out.An agent limited to read tools (Read, Glob, Grep) can run on any node. A dev server permits these sessions on any node and reports that fact.A webhook-started flow can reach an agent with Bash, Write or Edit only when its session runs in tier 1 without the plugin's secrets. An opted-out node cannot run that session. A harness-plugin secret bound to a host appears in the session as a placeholder; the sandbox inserts its value only into requests to that host.

A session that fails fails the job; it is not retried. Inside the session, the Claude Agent SDK retries a failed model call itself, up to four times, so a call reaches the provider at most five times, as for Pydantic AI agents. A session lost with its node is another matter: see below.

Copyright © 2026