# Muse Code Hooks Reference — Plugin-Only, Claude's Wire Schema, Outside the Sandbox

How hooks work in Muse Code today: a plugin capability behind an experimental flag, a Claude Code-compatible wire schema, and why hooks run outside the sandbox.

<div style="background:var(--acp-surface,#f8f9fb);border:1px solid var(--acp-border,#e2e5ea);border-radius:12px;padding:20px 24px;margin:24px 0;">
<p style="margin:0 0 10px;font-weight:600;">Just want the answer?</p>
<p style="margin:0 0 12px;">On the current 0.2.1 beta, a Muse Code hook isn't a file you hand-edit — it's a plugin capability, gated behind an experimental flag until it's approved once:</p>
<pre style="margin:0 0 12px;overflow-x:auto;"><code>MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins install @agenticcontrolplane/muse-code
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins approve @agenticcontrolplane/muse-code</code></pre>
<p style="margin:0;">Full walkthrough: <a href="/integrations/muse-code">Muse Code install guide</a>. Native control model: <a href="/controls/muse-code">Muse Code permissions, explained</a>.</p>
</div>

Muse Code, Meta's terminal coding agent, shipped its public beta with the strongest default posture of any harness in this reference series — an OS sandbox and tool approvals both on from the first run. Its hook system inherits none of that hardening. A hook command runs directly through your shell, outside both the sandbox and the approval layer, and the only way to register one on the shipped 0.2.1 binary is a flag Meta's own extending-Muse-Code docs don't mention. This is the reference for what's actually wired today, the wire format it speaks, and what running outside the sandbox means for a hook meant to be your control point.

## The docs and the binary describe two different front doors

Meta's documentation describes a `muse hooks` management CLI and a project-level `.muse/hooks.json` file. Neither is wired in the 0.2.1 beta: the CLI subcommand isn't exposed, and a hand-written project `hooks.json` is silently ignored — no error, no warning, it just never fires.

What's actually wired is the native **plugin system**, gated behind `MUSE_EXPERIMENTAL_PLUGINS=1`. A plugin (`.muse-plugin/plugin.json`) declares hooks as one of its capabilities; `muse plugins install` and `muse plugins approve` register and trust it. The flag only gates the *management* CLI — once a plugin is approved, its hooks fire in ordinary sessions with no flag required.

## Trust levels: project, user, managed

| Level | Where | Trust step |
|---|---|---|
| Project | `<project-root>/.muse/hooks.json` | `muse hooks trust <key>` per project |
| User | machine-wide settings file | explicit trust on first use |
| Managed | file at `managed_hooks_path` | pre-approved — no trust step |

Managed hooks are the first fleet-level control primitive documented in any of the harnesses in this series: a hook installed through the managed path runs with no per-user approval at all. Whoever controls that file controls what executes on every machine pointed at it — treat it with the same care as a sudoers file, not as a convenience setting.

## Installing the ACP plugin — the only path that works today

```bash
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins install @agenticcontrolplane/muse-code
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins approve @agenticcontrolplane/muse-code
```

Or via the installer, which detects Muse Code and does the same:

```bash
curl -sf https://agenticcontrolplane.com/install.sh | bash
```

## The wire schema: Claude Code's shape, verified

Every field name matches Claude Code's hook contract exactly. Input is snake_case — `hook_event_name`, `tool_name`, `tool_input`, `session_id`, `permission_mode`. Output is camelCase — a decision under `hookSpecificOutput.permissionDecision`. Exit code 2 plus stderr is a fallback deny channel, and an advisory `systemMessage` string is shown to the model without altering the decision.

| Event | Gateway call | Effect |
|---|---|---|
| `PreToolUse` | `POST /govern/tool-use` | deny blocks before the call runs |
| `PermissionRequest` | `POST /govern/tool-use` | settles Muse Code's own native prompt |
| `PostToolUse` | `POST /govern/tool-output` | audit + content scan after execution |
| `Stop` / `SubagentStop` | — | receipt only, no decision |

The event surface is broader than most harnesses here: `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreLLMCall`, `PostLLMCall`, `PreCompact`, `PostCompact`, `SubagentStart`, `SubagentStop`, `Stop`. The `PreLLMCall`/`PostLLMCall` pair and full subagent lifecycle aren't exposed by every harness in this series.

## Outside the sandbox, cleared environment — what that implies

Muse Code's own sandbox (Seatbelt on macOS, bubblewrap on Linux) is fail-closed and confines writes to the workspace plus temp; `.git`, `.muse`, and `.agents` stay read-only even from inside it. None of that reaches a hook. Hook commands execute directly through the shell, outside both the sandbox and the approval layer — the only hardening applied is a cleared environment with a small allowlist of variables passed through. MCP tools get the identical treatment: approved, not contained.

Two direct implications for anything running there:

1. **Credentials don't arrive via env.** An `ACP_BEARER_TOKEN` exported in your shell does not reach the hook process, because the environment is cleared before it runs. The plugin instead reads a credential file (`~/.acp/credentials`) written once by the installer, with optional config overrides in `~/.acp/config.json` for the same reason — `govern_base`, `agent_tier`, `check_timeout_ms`, and a `shadow` flag all live in that file rather than as env vars a hook could read.
2. **Every dependency is a supply-chain target.** A hook is a genuine extension point outside Muse Code's own strongest containment — which is why the ACP integration for it ships as a single file of plain ESM with zero dependencies and no build step, small enough to read start to finish before you trust it.

## Failure posture

Interactive sessions fail open, loudly — a stderr warning and a durable line in `~/.acp/lapse.log`. Unattended sessions (CI, `MUSE_HEADLESS`, or an explicit non-interactive `agent_tier`/`permission_mode`) fail closed instead, because nobody is watching the scrollback to notice a gap. An explicit deny holds in either posture.

## Beta churn — what to expect to change

The 0.2.1 release notes flag breaking changes ahead. The documented `muse hooks` CLI the binary doesn't yet expose is the clearest sign of where this is heading — expect the flag-gated plugin path used today to converge toward that documented surface in a later beta. Re-verify after every Muse Code update rather than assuming today's install command survives unchanged.

## Known limitations

- Hooks see tool calls, not tokens: model spend isn't metered for Muse Code today. No proxy or transcript path ships for it, so tool-call rows are what land in the audit trail, not per-call token cost.
- The hook fires exactly where containment stops — it's the control point and the edge of the sandbox at the same time. Design accordingly rather than assuming the sandbox's guarantees extend one step further.
- Beta docs currently describe a management surface the beta binary doesn't expose; re-check after upgrades.

## Comparing to Claude Code hooks

The wire schema is close to identical — this is, byte for byte, the same JSON shape Claude Code's hooks use. What differs is the install path (a flagged plugin system versus a settings file) and the sandbox relationship: Claude Code has no native OS sandbox to compare against, so "a hook runs outside the sandbox" is a Muse Code-specific caveat with no Claude Code analog. See the [Claude Code hooks reference](/blog/claude-code-hooks-reference) for the full contract on that side.

## Frequently asked questions



## Where to read more

- [Muse Code permissions, explained](/controls/muse-code)
- [Muse Code install guide](/integrations/muse-code)
- [Claude Code hooks reference](/blog/claude-code-hooks-reference)
- [Codex hooks reference](/blog/codex-cli-hooks-reference)
- [muse-code-acp-plugin source](https://github.com/agentic-control-plane/muse-code-acp-plugin)
- <!-- source: https://dev.meta.ai/docs/muse-code/extending.md --> [Extending Muse Code](https://dev.meta.ai/docs/muse-code/extending.md)
