{"slug": "show-hn-marrow-ai-a-no-account-seatbelt-that-stops-force-pushes-to-main", "title": "Show HN: Marrow AI – a no-account seatbelt that stops force-pushes to main", "summary": "Marrow AI released a no-account \"keyless seatbelt\" in version 3.9.103 of its @getmarrow/mcp server that stops seven kinds of risky action, including force-pushes to main, for users of its Claude Code and Cursor plugins. The plugins are installed from the getmarrow/plugins marketplace and require Node.js 18 or later on PATH; the Codex plugins pin the older @getmarrow/mcp 3.9.102 and do not yet include the keyless seatbelt. Marrow's hooks do not work on native Windows, where every action is blocked, and Cursor cannot install its plugin yet, though a one-click MCP-only link is available.", "body_md": "The `getmarrow` marketplace for Claude Code, Cursor and Codex. Each host's `marrow` plugin adds Marrow's approval hooks, the Marrow MCP server and a short skill to that host. Codex also has `marrow-advisory`, with no hooks. Cursor cannot install its plugin yet: see \"Install in Cursor\" below.\n\n| Host | Plugin folder | Marketplace file | \n|---|---|---|\n| Claude Code | `plugins/marrow` | `.claude-plugin/marketplace.json` | \n| Cursor | `plugins/marrow-cursor` | `.cursor-plugin/marketplace.json` | \n| Codex | `codex/marrow` (hooks),`codex/marrow-advisory` (no hooks; install one of the two, not both) | `.agents/plugins/marketplace.json` | \n\nEach plugin governs its own host only. The Claude Code plugin does not govern Cursor, even where Cursor imports Claude Code plugins: Cursor users will install the Cursor plugin once it can be installed (see \"Install in Cursor\" below).\n\nThe Claude Code and Cursor plugins run `@getmarrow/mcp` 3.9.103, which includes the free keyless seatbelt: with no account and no key, their hooks stop seven kinds of action (see \"Without a key: the keyless seatbelt\" below). With an account, Marrow holds risky actions for approval and keeps the audit trail. The Codex plugins pin `@getmarrow/mcp` 3.9.102 and do not include the keyless seatbelt; it comes to them with the next update.\n\n**Windows:** Marrow's hooks do not work on native Windows yet: every action is blocked. Use WSL, or wait for the fix.\n\nIn Claude Code:\n\n```\n/plugin marketplace add getmarrow/plugins\n/plugin install marrow@getmarrow\n```\n\nOr from your shell, in one command (Claude Code v2.1.292 or later):\n\n```\nclaude plugin install marrow --marketplace getmarrow/plugins\n```\n\nNo account or key is needed to install. Claude Code shows what the plugin adds before you confirm. If it asks, run `/reload-plugins`. The hooks and MCP server need Node.js 18 or later on `PATH`, as Marrow's `npx` hooks do today.\n\nCheck that `claude plugin marketplace list` shows `getmarrow` coming from `getmarrow/plugins`, and that `claude plugin list` shows exactly one `marrow@...` plugin.\n\nNot available yet: Cursor cannot install this plugin today, from its marketplace or from this repository. We will update this section when it can.\n\nOnly the MCP server, without hooks, is a one-click link (a page renders it as a link or an \"Add to Cursor\" button):\n\n```\ncursor://anysphere.cursor-deeplink/mcp/install?name=marrow&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIi0tcGFja2FnZT1AZ2V0bWFycm93L21jcEAzLjkuMTAzIiwibWFycm93LW1jcCJdfQ==\n```\n\nThe `config` value is base64 of `{\"command\":\"npx\",\"args\":[\"-y\",\"--package=@getmarrow/mcp@3.9.103\",\"marrow-mcp\"]}`, pinned, with no key and no `env`. The MCP server alone is advisory: approvals are enforced only by the plugin's hooks. Unlike the plugin, the link starts the server through `npx`, which can pick up a copy of `@getmarrow/mcp` that a project's `node_modules` claims at the same version. Prefer the plugin.\n\n| Component | Claude Code ( `plugins/marrow` ) | Cursor ( `plugins/marrow-cursor` ) | \n|---|---|---|\n| Hooks | `UserPromptSubmit` ,`PreToolUse` ,`PostToolUse` ,`PostToolUseFailure` ,`Stop` , and the async`PermissionRequest` and`PostToolBatch` hooks: the events`npx @getmarrow/install` writes into`.claude/settings.json` . Their tool matcher sends Marrow every tool call the installer's matcher does, and skips only this plugin's own 17 Marrow tools by exact name. A warn-only`SessionStart` check says when Marrow is also set up outside the plugin or another plugin named`marrow` is enabled. | The ten hooks `npx @getmarrow/install` 0.1.70 writes into`.cursor/hooks.json` :`preToolUse` ,`beforeShellExecution` and`beforeMCPExecution` (15 s,`failClosed` ),`postToolUse` ,`postToolUseFailure` ,`afterShellExecution` ,`afterMCPExecution` ,`stop` ,`sessionStart` and`beforeSubmitPrompt` , with the installer's matchers byte for byte. | \n| MCP server | `.mcp.json` : server`marrow` . Its tools are on demand. | `mcp.json` : server`marrow` . Its tools are on demand. | \n| Skill | `skills/marrow` : when to call Marrow, holds and approvals, the key step, update commands. | `skills/marrow` : the same rules for Cursor. | \n\nBoth run the pinned `@getmarrow/mcp` 3.9.103 entrypoints, the same ones the installer writes, through the same launcher. The plugins add no policy of their own.\n\nHooks are cooperative, client-reported control. They act only while the plugin is enabled and the host runs them. A configured hook never proves coverage or enforcement on its own.\n\n**Hook commands.** Every hook command is one string that names the plugin root variable and the entrypoint inline (`node \"${CLAUDE_PLUGIN_ROOT}/scripts/marrow-launch.cjs\" claude-pre-action-hook`, `node \"${CURSOR_PLUGIN_ROOT}/scripts/marrow-launch.cjs\" cursor-pre-action-hook`, `${PLUGIN_ROOT}` on Codex). None is relative to your project and none relies on separate `args`, which a host that imports the plugin may drop. The launcher starts the MCP server only for the explicit `mcp` argument in the plugin's MCP config; started with no argument it refuses (exit 2) and starts nothing. `tools/check.cjs` enforces both.\n\n**Cursor: how Marrow's own tools are told apart.** Cursor's `preToolUse` names an MCP tool `MCP:<tool>`, with no server, so a name cannot prove which server it came from. The plugin uses `@getmarrow/install` 0.1.70's matchers byte for byte: every label that holds `MCP:` reaches the hook, `MCP:marrow_<anything>` included, and no MCP label is skipped by name. The pinned hook then allows Marrow's own tools only for the server exactly `marrow` and an exact registered tool name; `Marrow`, `MARROW`, padded and re-cased names go the normal path. `beforeMCPExecution` carries the server name and has no matcher. The plugin sends every MCP call there, Marrow's own included, to the pinned hook, with no fast-allow guard, so it never allows more than the installer's guard does.\n\nWhen a pre-action hook has no usable Marrow key and the machine shows no sign of a Marrow account (for example an owner key file, a claim marker or Marrow's local approval state), the hook runs a local seatbelt instead of holding every protected action. It comes with `@getmarrow/mcp` 3.9.103 and later, which the Claude Code and Cursor plugins run. It needs no account and no key, and it makes no network call.\n\n- **It stops seven kinds of action:** a change to Marrow's hooks, settings or plugins, or an owner-only Marrow command (S0); a force-push or delete of a protected branch (S1); a production deploy (S2); a package publish (S3); reading credentials or secrets, including a project`.env` file other than a template (S4); a recursive delete outside the workspace (S5); and decoded data run by a shell or interpreter (S-x). The stop is the host's own deny, with text that tells the agent to leave the action to the machine owner. It reads shell commands, each host's own file tools and MCP calls. It fails closed: an internal error, a check that runs past 300 ms, or input the host cut short stops the action.\n- **Everything else gets no Marrow decision, so the host's own approval step decides:** Claude Code gets an empty answer. Cursor asks with its own approval prompt before a shell command that a hook without a key used to hold (for example`git commit` or`git push` ); Cursor's other tools (MCP calls included) keep holding what a hook without a key held before. A tool the seatbelt does not recognise, or MCP arguments it cannot read, never gets the seatbelt's pass: the hook gives the answer a hook without a key gave before, a hold where that held.\n- **Local receipts:** each seatbelt decision is written to`~/.marrow/seatbelt/receipts/` on this machine, as codes and keyed hashes, plus the built-in tool name and shell program names from fixed lists (any MCP tool is recorded only as`mcp` ); no command text, argument, path or URL. They are kept for up to 90 days or 50 MB. Nothing is uploaded.\n- **Folder permissions:** the seatbelt runs only where your home folder and the agent settings folders (such as`~/.claude` ) can be written by you alone. On Linux, a folder that your own private group can also write (the default with umask 002) still counts. Any other folder keeps every hold, and the hold says that Marrow's keyless mode is off here.\n- **It is a seatbelt, not a vault:** it runs on the machine as your user and makes no network call. A determined agent can get around a local check, for example with a script it writes now and runs later, an interpreter, or a tool path with no hook.\n- **With an account:** Marrow holds risky actions for approval and keeps the audit trail. A machine linked to an account but with no working key keeps every hold it had, and the seatbelt stops apply too. With a working key the answers and requests are unchanged; the seatbelt only watches and records would-stops locally.\n\nThe Codex plugins pin `@getmarrow/mcp` 3.9.102 and do not include the keyless seatbelt; without a key their hooks hold protected actions with \"Marrow has no API key\" (see \"Codex\" below), and the seatbelt comes to them with the next update. The Marrow MCP server needs a key and does not start without one.\n\nTo connect an account, create an agent key at [https://getmarrow.ai](https://getmarrow.ai), then keep it in a file in your own terminal:\n\n```\nmkdir -p ~/.marrow && chmod 700 ~/.marrow\n# put MARROW_API_KEY=<your_key> in ~/.marrow/env\nchmod 600 ~/.marrow/env\n```\n\nOr set `MARROW_API_KEY` in the environment that starts the host. Then run `/reload-plugins` in Claude Code, or restart Cursor. Never paste a key into chat.\n\nThe plugins carry the hooks, so the hold message's \"run the installer activate command\" step is not needed for Claude Code or Cursor in a project that uses the plugin. That command (`MARROW_API_KEY=<your_key> npx -y @getmarrow/install@latest activate`), like the installer's `--repair`, writes a second copy of the hooks there. To check the key and connection, run `npx -y @getmarrow/install@latest doctor`. It only reads. Installer 0.1.71 and later count the Claude Code plugin's hooks; 0.1.70 and earlier report Claude Code hooks as missing, because the plugin holds them. The Cursor plugin is not counted, so doctor can report Cursor hooks as missing while the plugin holds them.\n\n- `package.json` and`package-lock.json` (the same files in both the Claude Code and the Cursor plugin) pin`@getmarrow/mcp` 3.9.103 and`@getmarrow/sdk` 3.7.65 with sha512 integrity, equal to`@getmarrow/install` 0.1.71's`src/pins.js` . The Codex plugins pin`@getmarrow/mcp` 3.9.102 and`@getmarrow/sdk` 3.7.65, equal to`@getmarrow/install` 0.1.70's`src/pins.js` .\n- Hooks start the package with `node` , not`npx` . Each start checks every file against`scripts/mcp-manifest.json` . Claude Code installs the lockfile into the plugin's cached copy with install scripts disabled. If that copy is missing or changed, or on Cursor, which installs no plugin dependencies, the launcher installs the same lockfile once (`npm ci --ignore-scripts` , outside your project) and checks that copy the same way. Claude Code keeps it in the plugin's data folder (`~/.claude/plugins/data/` , removed on uninstall), Cursor in`~/.marrow/cursor-plugin` (mode 700), Codex in`~/.codex/plugins/data/marrow-getmarrow/` . Nothing is fetched by version at run time and nothing runs from your project's`node_modules` .\n- If no verified copy can be loaded (for example, offline on first use), the pre-action hook holds tool calls with a \"Marrow plugin:\" message until it can, and the other hooks report a non-blocking error.\n- Each plugin sets `version` , so the host keeps you on the installed copy until a new version is published and you update. Third-party Claude Code marketplaces do not auto-update unless you turn that on.\n- To update the Claude Code plugin after Marrow reports a client update:\n\n```\nclaude plugin marketplace update getmarrow\nclaude plugin update marrow@getmarrow\n```\n\nCursor cannot install Marrow yet, so there is nothing to update there.\n\n```\nclaude plugin uninstall marrow@getmarrow\nclaude plugin marketplace remove getmarrow\n```\n\nCursor cannot install Marrow yet; if you have a package copy from an earlier try, remove it with `rm -rf ~/.marrow/cursor-plugin`. In Codex, see \"Update and remove\" below.\n\nThe hooks and the MCP server live inside the plugin, so they go with it. What is left, exactly:\n\n- **Claude Code.** Installing writes`enabledPlugins` (and, from the marketplace add,`extraKnownMarketplaces` ) in`~/.claude/settings.json` ; it writes no hook entries and does not touch your other settings. Uninstall removes the`enabledPlugins` entry and the plugin's data folder (`~/.claude/plugins/data/` ). The cached plugin copy stays, marked`.orphaned_at` , until Claude Code's own cleanup removes it.`claude plugin marketplace remove getmarrow` removes the marketplace registration.\n- **All hosts.** Marrow's runtime state under`~/.marrow` stays: for example`session-loop-guard/state.json` , and the cache, spool, health and host-approval files that build up in use. Your key file`~/.marrow/env` stays too. Delete the files you no longer want; nothing is deleted for you.\n\nThe same repository is a Codex marketplace (`.agents/plugins/marketplace.json`) with two plugins:\n\n| Plugin | What it adds | \n|---|---|\n| `marrow` | Marrow's Codex hooks ( `SessionStart` ,`UserPromptSubmit` ,`PreToolUse` ,`PostToolUse` ,`SessionEnd` ), the Marrow MCP server and the skill. The hooks run the pinned`@getmarrow/mcp` 3.9.102 Codex entrypoints, the ones`npx @getmarrow/install` writes into`.codex/hooks.json` . Their tool matcher is`*` : every tool call reaches the hook, this plugin's own Marrow tools included, and the plugin exempts no tool by name. The pinned 3.9.102 hook then skips a call without a check when its label is exactly`mcp__marrow__<name>` for one of the 58 tool names the`marrow` server registers. It reads the label only and cannot tell which server produced it. | \n| `marrow-advisory` | Advisory only: the Marrow MCP server and the skill, with no hooks. Nothing is held; Marrow sees an action only when the agent calls a `marrow_*` tool. | \n\nInstall one of them, not both: both name their MCP server `marrow`. Codex accepts both without a warning, and one server `marrow` then wins.\n\n```\ncodex plugin marketplace add getmarrow/plugins\ncodex plugin add marrow@getmarrow\n```\n\nNo account or key is needed to install. Start a new Codex session, then open `/hooks` and review and trust the Marrow hooks: Codex does not run plugin hooks until you trust them, and asks again whenever they change. On the first session start the plugin installs its pinned copy of `@getmarrow/mcp` (from `package-lock.json`, install scripts off) into `~/.codex/plugins/data/marrow-getmarrow/` and checks it against `scripts/mcp-manifest.json`; until a verified copy loads, `PreToolUse` holds tool calls with a \"Marrow plugin:\" message. Node.js 18 or later must be on `PATH`.\n\nKey: without one, routine tool calls run and protected actions are held with \"Marrow has no API key\". These plugins pin `@getmarrow/mcp` 3.9.102, which has no keyless seatbelt; it comes to them with the next update. Codex passes a plugin MCP server only `MARROW_API_KEY` from your environment (by name; no key is stored in any config), so the simplest setup is the key file:\n\n```\nmkdir -p ~/.marrow && chmod 700 ~/.marrow\n# put MARROW_API_KEY=<your_key> in ~/.marrow/env\nchmod 600 ~/.marrow/env\n```\n\nUpdate and remove:\n\n```\ncodex plugin marketplace upgrade getmarrow\ncodex plugin remove marrow@getmarrow      # or marrow-advisory@getmarrow\nrm -rf ~/.codex/plugins/data/marrow-getmarrow\n```\n\n`codex plugin remove` leaves the package copy in `~/.codex/plugins/data/marrow-getmarrow` (about 3 MB); the `rm` step deletes it. The runtime state under `~/.marrow` and your key file stay, as above.\n\n`npx @getmarrow/install` writes Codex hooks too, and what they do depends on its version:\n\n- **0.1.70 and earlier.** Their tool-hook matcher uses a lookahead that Codex rejects (`invalid matcher` ), so their`PreToolUse` and`PostToolUse` hooks never load and no Codex tool call is checked. Only their context and session hooks run. For protection in Codex, use this plugin.\n- **0.1.71 and later.** The installer writes the matcher`*` , so its Codex hooks send every tool call to Marrow's check, Marrow's own tools included. The check itself is`@getmarrow/mcp` : from 3.9.103 it skips Marrow's own tools only when the host's own configuration proves that the`marrow` server is the official Marrow MCP server (the`npx -y @getmarrow/mcp` entry with no project or environment npm setting that could change where npx fetches it, the installed`marrow-mcp` , the installer's verified runtime, or this plugin, verified file by file and installed alone, with no other server that could give the same label). A look-alike server or plugin, or one Marrow cannot prove, is checked like any other tool. Installer 0.1.71 pins 3.9.103.\n- **This plugin's own hooks** run the`@getmarrow/mcp` 3.9.102 it pins. They get every tool call (`*` ), and 3.9.102 skips by label alone (see the table above). They do not include the keyless seatbelt; it comes to them with the next update. A plugin release cannot pin an MCP that already carries that release's pins, so a Codex plugin pins an MCP older than the one that carries its pins ([docs/PLUGIN-RELEASE.md](https://github.com/getmarrow/plugins/blob/master/docs/PLUGIN-RELEASE.md) ).\n\nUse the plugin or the installer for Codex, not both. If the installer's `.codex/hooks.json` is also there, its context and session hooks run twice next to the plugin's, and from installer 0.1.71 its tool hooks run next to the plugin's as well, so every tool call is checked by two separate hooks; keep one copy. Hooks are cooperative, client-reported control.\n\nThe bundled skill (`skills/marrow/SKILL.md` and `references/hosts.md` in both packages) is the `getmarrow/skills` copy byte for byte. Copy it from the skills repo when that changes; `--skills <checkout>` below fails if either package differs.\n\nMaintainers: `node tools/codex-check.cjs <marrow-install>/src/pins.js <marrow-mcp>/src/cli.ts <marrow-install>/src/installer.js --skills <getmarrow/skills checkout> --rust <wasm>` checks both packages and the matcher's name table. Build the Rust checker once with `cargo build --release --target wasm32-wasip1` in `tools/codex-rust-matcher` (it compiles the matcher with the `regex` crate, the engine Codex uses). Change pins with `node tools/codex-pin-manifest.cjs <dir>` (same steps as above, for `codex/marrow`, then copy `package.json` and the lockfile into `codex/marrow-advisory`), and keep `OWN_TOOLS` in `tools/codex-matcher.cjs` equal to the default tool set. The tool matcher is the wildcard `*`, so no matcher is generated and no tool name belongs in `hooks/hooks.json`; `tools/codex-check.cjs` fails if one does.\n\nGemini CLI, Windsurf and other hosts get Marrow from the installer, run by the owner in a terminal: `MARROW_API_KEY=<your_key> npx -y @getmarrow/install@latest activate`, then `npx -y @getmarrow/install@latest doctor`. The skills for those agents are at [https://github.com/getmarrow/skills](https://github.com/getmarrow/skills).\n\nThe Codex plugins are also pinned by `@getmarrow/mcp`: read [docs/PLUGIN-RELEASE.md](https://github.com/getmarrow/plugins/blob/master/docs/PLUGIN-RELEASE.md) for the order before you change any of their six pinned files or bump their version. CI (`.github/workflows/codex-plugin-pins.yml`) fails on a pinned file that has no matching pin.\n\n1. Set the new exact versions in `plugins/marrow/package.json` , then`npm install --package-lock-only --ignore-scripts` there.\n2. In an empty directory with copies of `package.json` and`package-lock.json` :`npm ci --ignore-scripts` .\n3. `node tools/pin-manifest.cjs <that directory>` .\n4. Copy `package.json` ,`package-lock.json` ,`scripts/marrow-launch.cjs` and`scripts/mcp-manifest.json` to`plugins/marrow-cursor` (`tools/check.cjs` requires identical bytes).\n5. Raise `version` in`plugins/marrow/.claude-plugin/plugin.json` and`plugins/marrow-cursor/.cursor-plugin/plugin.json` . A host keeps a cached copy while the version is unchanged, so a pin change without a version change never reaches users.\n6. If the new MCP release changes its default tool set, update `OWN_TOOLS` in`tools/check.cjs` and the matcher in`plugins/marrow/hooks/hooks.json` . If the installer changes its Cursor hooks, update`plugins/marrow-cursor/hooks/hooks.json` and`CURSOR_EXPECTED` .\n7. Run the checks:\n  - `node tools/check.cjs <marrow-install>/src/pins.js <marrow-mcp>/src/cli.ts <marrow-mcp>/dist/hook-contract.js [<candidate marrow-mcp>/dist/hook-contract.js ...] [--installer-cursor-hooks <hooks.json the installer writes for Cursor>]` (use`src/tool-catalog.ts` for 3.9.102 and later in place of`src/cli.ts` )\n  - `node tools/codex-check.cjs <marrow-install>/src/pins.js <marrow-mcp>/src/cli.ts <marrow-install>/src/installer.js --skills <getmarrow/skills checkout>`\n  - `node tools/test-launcher.cjs claude` and`node tools/test-launcher.cjs cursor`\n  - `claude plugin validate plugins/marrow --strict`\n8. Tag the release (`v<version>` ).\n\nThe Claude Code and Cursor pins follow the `@getmarrow/install` release (`src/pins.js`): the same MCP version, SDK version and integrity. The Codex pins are the ones `@getmarrow/mcp` carries for each Codex plugin version ([docs/PLUGIN-RELEASE.md](https://github.com/getmarrow/plugins/blob/master/docs/PLUGIN-RELEASE.md)).", "url": "https://wpnews.pro/news/show-hn-marrow-ai-a-no-account-seatbelt-that-stops-force-pushes-to-main", "canonical_source": "https://github.com/getmarrow/plugins", "published_at": "2026-10-21 13:30:05+00:00", "updated_at": "2026-10-21 13:43:09.681924+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "agent-protocols"], "entities": ["Marrow AI", "@getmarrow/mcp", "Claude Code", "Cursor", "Codex", "Node.js"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-marrow-ai-a-no-account-seatbelt-that-stops-force-pushes-to-main", "markdown": "https://wpnews.pro/news/show-hn-marrow-ai-a-no-account-seatbelt-that-stops-force-pushes-to-main.md", "text": "https://wpnews.pro/news/show-hn-marrow-ai-a-no-account-seatbelt-that-stops-force-pushes-to-main.txt", "jsonld": "https://wpnews.pro/news/show-hn-marrow-ai-a-no-account-seatbelt-that-stops-force-pushes-to-main.jsonld"}}