# Muse Code's hooks aren't where the docs say — we found where they actually live

Meta's Muse Code documents a muse hooks CLI its shipped binary doesn't have. Underneath is a plugin system behind a feature flag, speaking Claude Code's exact hook contract. Here's the reverse-engineering, the ACP plugin we built on it, and where Muse lands on the cross-harness coverage table.

[Muse Code](https://dev.meta.ai/docs/muse-code) — Meta Superintelligence Labs' terminal coding agent, in beta since August 5 — now has ACP support via [@agenticcontrolplane/muse-code](https://github.com/agentic-control-plane/muse-code-acp-plugin) ([npm](https://www.npmjs.com/package/@agenticcontrolplane/muse-code)).

Getting there meant answering a question the docs don't: *where are Muse Code's hooks, actually?* Because the documented interface and the shipped binary disagree — and the answer turned out to be more interesting than a normal integration.

## The docs describe a door that isn't there

Meta's docs describe a hook system with a `muse hooks` management CLI — `list`, `validate`, `trust`, `run --fixture` — and a `.muse/hooks.json` registration file. We installed the current beta (0.2.1) and went looking for it. The CLI isn't there:

```
$ muse hooks list
invalid TUI options: unknown argument `hooks`
```

A project `.muse/hooks.json` is silently ignored. The documented front door is real on paper and absent in the binary. Beta means beta — but a control layer can't be built against a door that doesn't open, so we kept looking.

## Where the hooks actually live

The hook *engine* is in the binary — you can see the lifecycle events (`PreToolUse`, `PermissionRequest`, `PostToolUse`, `Stop`, and the rest) and the decision vocabulary right in it. What's missing is only the management CLI. So the question became: what *does* register a hook?

The binary answers its own question. It ships a built-in plugin-authoring skill, and inside it a file named `native-plugin-contract.md` — Meta's own spec for the interface that's actually wired. Hooks ship as **capabilities of a native plugin**, gated behind an experimental flag:

```bash
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins install ./my-plugin --scope user
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins approve my-plugin
```

A plugin is a directory with a `.muse-plugin/plugin.json` manifest; each hook is a capability naming a lifecycle event and an argv command. The flag gates only the *management* CLI — once a plugin's hooks are installed and approved, they fire in ordinary sessions with no flag set. That's the door. It's just not the one the docs point at yet.

## It speaks Claude Code's contract, exactly

Here's the part that made the integration fast. We drove a probe hook through Muse's own capability runner and read back what it accepts, field by field. The wire contract is Claude Code's hook schema — not similar, the same:

- **Payload in** (snake_case JSON on stdin): `hook_event_name`, `tool_name`, `tool_input`, `session_id`, `permission_mode`, `cwd`.
- **Decision out**: a `hookSpecificOutput` object with `permissionDecision` (`deny` / `ask` / `allow`) and `permissionDecisionReason`. A bare `{}` is allow. `PostToolUse` blocks with a legacy `{ "decision": "block", "reason": … }`. Exit code 2 with stderr is the fallback block channel. Unknown output keys *fail the hook* — so there's no guessing; the parser tells you precisely what it takes.

If you've written a Claude Code hook, you've written a Muse Code hook. That's a real gift to anyone building tooling across harnesses, and it's the reason our plugin is thin.

## What the ACP plugin does

The plugin is one zero-dependency ESM file. Every tool call Muse makes gets policy-checked against your ACP workspace before it runs:

| Muse event | ACP endpoint | Effect |
|---|---|---|
| `PreToolUse` | `POST /govern/tool-use` | allow / ask / deny before the tool runs |
| `PermissionRequest` | `POST /govern/tool-use` | a policy deny settles Muse's own approval prompt; anything else lets it proceed |
| `PostToolUse` | `POST /govern/tool-output` | output scanning; a server block becomes feedback the model sees |
| `Stop` | — | one session receipt with a deep link to the timeline |

We verified every one of those paths against Muse Code's own `muse plugins hook test` runner — a real `rm -rf /` through the real production gateway comes back denied with the hardline-floor reason attached, a benign read passes, an approval request is settled by policy. Not a mock of the harness: the harness's own hook runtime, driving our actual plugin.

One design note worth its own line. Muse runs hooks with a **cleared environment** — a deliberate, good hardening choice. It also means an `ACP_BEARER_TOKEN` you export never reaches the hook. So the plugin reads its credential from `~/.acp/credentials` (written once by [the installer](/install-explained), free for individuals) and takes operational overrides from `~/.acp/config.json`. Zero dependencies isn't an aesthetic here: a hook running outside Muse's sandbox with your credential nearby is a supply-chain target, and the smallest possible attack surface is one file you can read in two minutes.

## Where Muse Code lands on the coverage table

*(The living version of this table is at [/coverage](/coverage).)*

Muse Code intercepts everything through one hook pipeline, can express a real ask (its `PermissionRequest` maps cleanly to policy), and — because `PostToolUse` can turn a server block into model-visible feedback — can act on output rather than just watch it. It lands in strong company: alongside Claude Code and DeepSeek Harness, it can answer all three coverage questions with a yes.

That's not our plugin being clever. It's Meta shipping a genuinely strong default posture — OS sandbox and approvals on from the first run, an LLM approval judge, a hook engine that borrowed the right contract — and then documenting a management CLI that hasn't caught up to it. We built against what's actually there: the plugin system is the wired path, and it works today.

## Install

```bash
npm install -g @agenticcontrolplane/muse-code
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins install "$(npm root -g)/@agenticcontrolplane/muse-code" --scope user
MUSE_EXPERIMENTAL_PLUGINS=1 muse plugins approve acp
```

Full guide, including the cleared-environment credential path and the config overrides: **[/integrations/muse-code](/integrations/muse-code)**. The control-model reference for Muse Code — approval modes, the LLM judge, the sandbox, and where the docs run ahead of the binary — is at **[/controls/muse-code](/controls/muse-code)**.

Muse Code is days into a beta that promises breaking changes; treat the plugin the same way — early, tested against the harness's own runtime, tracking a fast-moving target. The interception surface underneath is one of the best we've integrated, and it was hiding one flag away from where the docs said to look.
