Integration models by agent
Stdin hook subprocess agents
For these nine agents, the protection is the runtimehook <flag> command, which reads JSON from stdin. Each agent names its pre-execution event and its command tool differently, and each expects a different deny shape on stdout:
Because these agents use different events and deny formats, CC Safety Net emits the correct shape per agent. Gemini CLI, for example, expects a
decision/systemMessage object with exit 0 rather than Claude Code’s hookSpecificOutput. You do not need to configure the output format because the flag selects it.Use the shared Coding CLI hook
hook --coding-cli (short flag -cc) is the canonical name of the Claude-shaped hook. It is called “Coding CLI” rather than “Claude Code” because the entry point accepts that payload shape from any agent that sends it.
hook --claude-code is accepted as a legacy alias and is not advertised in hook --help. Do not use it in new configuration.
Separately, three agents keep legacy top-level forms that skip the hook word: cc-safety-net -cc / --claude-code, -gc / --gemini-cli, and -cp / --copilot-cli. The canonical --coding-cli is not a top-level flag. A bare cc-safety-net --coding-cli errors with Unknown option: --coding-cli.
The
statusline command is unrelated to the hook and still requires --claude-code or -cc. See Status line.Hook configuration locations
CC Safety Net writes files directly for five of the nine agents. Four use hook configuration entries, and Hermes Agent uses a managed plugin. The other four use their own plugin or extension distribution channel, which then invokes the stdin hook:
For a Kimi Code
Bash call, tool_input.cwd becomes the execution directory when it is present. It must be a non-empty string that resolves inside the session cwd; an invalid or escaping value fails closed. GitHub Copilot CLI routes powershell and PowerShell calls to the PowerShell analyzer.
For a Grok Build call, the trusted root is workspaceRoot, or cwd when workspaceRoot is absent. cwd must canonicalize to a directory inside that root, and an absent or empty cwd is treated as .. A root that cannot be canonicalized, or a cwd outside it, fails closed. A toolInputTruncated: true envelope also fails closed, because Grok Build truncates tool input at 128 KB and the truncated command cannot be analyzed.
How Codex reaches the hook
Codex loads CC Safety Net as thecc-safety-net@cc-marketplace plugin from the cc-marketplace marketplace. The plugin is packaged in Codex’s own format, and its manifest points at hooks/codex.json, which registers the PreToolUse hook. The same plugin carries the cc-safety-net skill.
Codex will not run an untrusted hook, so the hook stays inert until it is marked trusted inside Codex. See Installation for that step.
How Hermes Agent reaches the hook
Hermes does not run the hook command from a hook config. CC Safety Net installs a managed Python plugin that Hermes loads in process, and the plugin registers apre_tool_call handler. For each supported tool call, the handler runs npx -y cc-safety-net hook --hermes-agent and writes the call as JSON to its stdin. Empty stdout means the call is allowed. A { "action": "block", "message": … } object blocks it, and Hermes shows the message to the model as the tool result.
The plugin forwards four tools: terminal for command analysis, write_file and patch for protected writes, and read_file for protected reads. Calls to any other Hermes tool are not forwarded and get no decision.
Hermes ignores a plugin callback that raises, so the plugin turns every failure into an explicit block itself: npx missing from PATH, a spawn failure, a timeout (30 seconds, killing the analyzer’s whole process group), a non-zero exit, or unreadable or unexpected output. A Hermes session directory that the plugin cannot read also blocks. Analyzing the wrong directory would clear every path-scoped protection.
Two directory details matter here. For terminal calls the plugin first reads Hermes’ per-session cwd record, which holds the session’s cd state. On the first command, before that record exists, it uses TERMINAL_CWD, then the Hermes process directory. A terminal call with an unusable workdir fails closed. The analyzer subprocess starts from the home directory rather than Hermes’ working directory, so npx cannot resolve a repository-local cc-safety-net in place of the real one.
Every managed file starts with a header line marking it as CC Safety Net’s; the installer refuses to overwrite a file without it. Uninstall runs hermes plugins disable cc-safety-net before removing the files, because Hermes only resolves a plugin that is still on disk. In both directions the plugin change is inert until Hermes restarts. See Installation for the setup and removal commands.
Unlike Pi, doctor detects Hermes Agent entirely from disk: the managed plugin directory plus the plugins.enabled list in Hermes’ config.yaml. No runtime probe is involved.
Agent-loaded plugins
OpenClaw
OpenClaw loads CC Safety Net in process as a native OpenClaw plugin. The plugin ships as a packaged directory with three files. The runtime entryindex.js is a self-contained bundle with every dependency inlined because a local directory install gets no node_modules. The openclaw.plugin.json manifest is validated before OpenClaw loads any code. A package.json points openclaw.extensions at the entry. There is no hook flag and no JSON-over-stdio.
The plugin registers a before_tool_call handler matched to the exec tool. Only the untagged shell exec is analyzed. An exec event with a toolKind discriminator, such as Code Mode’s JavaScript exec, gets no decision because its command field is not a proven shell-command mapping. No other OpenClaw tool is forwarded to the guard. The handler returns either no decision (allow) or { block: true, blockReason }; it never rewrites tool parameters.
It resolves the agent’s workspace directory through OpenClaw’s runtime API and uses it as both the policy and execution directory; a workdir in the call is resolved contained within that workspace. CC Safety Net fails closed on a malformed event, a missing or empty command, a workspace it cannot resolve, a workdir outside the workspace, or a call already cancelled. An exec call whose host is anything other than auto or gateway is also blocked. gateway is proven local. A call with host: "auto" (or no host at all) is analyzed with local Gateway semantics, but the plugin does not check where OpenClaw routes it. The sandbox case is listed in Known limitations.
OpenClaw owns its plugin state, so installation drives OpenClaw’s own CLI: openclaw plugins install <packaged dir> --force, then openclaw plugins enable cc-safety-net. Before any --force command, CC Safety Net verifies that the target extension directory holds only its own managed files. It cannot overwrite or delete a plugin that is not provably its own. After installation, it runs openclaw plugins inspect cc-safety-net --runtime --json and treats only a loaded status as success. Otherwise, a broken enabled plugin could install cleanly and then protect nothing.
The installed copy lives at <state dir>/extensions/cc-safety-net/, where the state dir is OPENCLAW_STATE_DIR when set, else the directory holding OPENCLAW_CONFIG_PATH, else ~/.openclaw. Enablement lives in OpenClaw’s own config (openclaw.json in the state dir, or OPENCLAW_CONFIG_PATH): the global plugins.enabled switch, the plugins.allow and plugins.deny lists, and the per-plugin plugins.entries.cc-safety-net.enabled entry all take part. If plugins.allow is set, it must also list cc-safety-net.
Restart the OpenClaw Gateway after installing or uninstalling. The manifest activates the plugin at startup (activation: { onStartup: true }), so a running Gateway does not pick up the change. See Installation for the setup and removal commands.
OpenCode
OpenCode loads CC Safety Net in process as a plugin object implementing OpenCode’s own@opencode-ai/plugin contract, declared in the plugin array of ~/.config/opencode/opencode.json (or .jsonc). The plugin does two things:
- Implements
tool.execute.before, which OpenCode calls ahead of every tool execution. CC Safety Net analyzes the call and throws a denial when the command is destructive; there is no JSON-over-stdio. - Implements the
confighook to inject CC Safety Net’s builtin commands into the OpenCode command set, without overwriting commands you have already defined.
bash tool, the plugin selects the analyzer dialect from OpenCode’s shell setting. If the setting is not a string, it defaults to PowerShell on Windows and uses SHELL elsewhere. Recognized powershell and pwsh executables select PowerShell; recognized POSIX shells select POSIX; other values use automatic detection. A supplied workdir becomes the execution directory only when it resolves to a readable, searchable directory. On Windows, documented /C:, /C, /cygdrive/C, and /mnt/C forms are normalized, while other slash-rooted paths pass through for OpenCode to resolve.
OpenCode can serve a stale cached copy of the plugin, so a wiring change may not take effect until the cache is cleared. See Installation.
The Pi extension
Pi loads CC Safety Net as an in-process extension, declared through the package’spi.extensions field and recorded as the package source npm:cc-safety-net in ~/.pi/agent/settings.json. The extension does two things:
- Registers a
tool_callevent handler (pi.on('tool_call')). The handler inspects Pi tool calls before they run and returns a block result when a command or path violates the policy. Nothing crosses a process boundary. - Registers a
/cc-safety-netbuiltin command for managing rulebooks interactively inside Pi.
Tools Pi protects
The built-inbash tool is Pi’s only command-tool adapter. Its command runs against the session cwd. The obsolete custom Shell adapter is not supported. Pi’s non-command tools still reach path and secret protection. In particular, find is classified as a read-only glob tool, so its pattern and path values are inspected without treating the search itself as a write. If a command call or its session cwd is malformed, CC Safety Net fails closed.
Detecting Pi
Because there is no hook config file to inspect, thedoctor command detects Pi with a runtime probe (it spawns pi and asks whether the extension is loaded and enabled). This is why a Pi status in doctor may read n/a even when Pi is installed, if the probe cannot run.
The Amp Code event plugin
Amp Code loads CC Safety Net as a personal plugin: acc-safety-net directory in your Amp account’s hosted Personal Plugins repository, holding the self-contained entry file index.ts. The plugin subscribes to Amp’s tool.call event through the @ampcode/plugin API and returns either allow or reject-and-continue with a message. Like Pi, OpenClaw, and OpenCode, it runs in process. Because a personal plugin follows the account rather than one machine, it also covers threads that run on a remote executor such as an Amp Orb.
The Amp workspace root (amp.system.workspaceRoot) is the configuration directory. When a shell tool call carries a string dir, that value becomes the execution directory: a relative path resolves against the workspace root, the result goes through realpath, and it must exist and be a directory. Windows namespace paths are rejected. A dir that cannot be resolved fails closed, and the call is not analyzed for destructive commands. The denial no longer reports an unexpected analyzer failure. It names the working directory as the cause and tells the agent to use a directory that already exists and is accessible, or to create the missing one first. Unlike OpenClaw’s workdir, the resolved directory is not required to stay inside the workspace. Amp legitimately points it at its own skills cache or a sibling repository, and the same work written as cd <dir> && … already runs there. The workspace root stays the configuration directory, so the project’s own rule configuration still applies. The git-metadata guard anchors to both the execution directory and the configuration directory, so the workspace’s own .git keeps its protection while the command runs elsewhere. See Git metadata.
install --amp publishes the artifact to that repository. It checks that the account has a writable Personal Plugins repository (amp plugins repositories --json), clones it into a throwaway checkout (amp clone user-plugins), writes cc-safety-net/index.ts, then commits and pushes. Staging uses an explicit pathspec (git add -- cc-safety-net/index.ts) rather than the directory, so a gitignored plugin path stops the install instead of staging nothing. Install and uninstall only ever write or git rm that one entry, so files you keep beside it in the directory are never touched.
A managed header identifies the entry as CC Safety Net’s and marks it as safe to replace. Install and uninstall both refuse a cc-safety-net entry that is a symlink or not a directory, and an index.ts that is a symlink, not a regular file, or unmanaged. Releases before the directory layout published cc-safety-net.ts at the repository root; install removes a managed one in the same commit, and an unmanaged one fails the install.
A local plugin masks the personal one, so install also cleans up under ~/.config/amp/plugins/. It removes a managed legacy cc-safety-net.ts, and a hand-copied cc-safety-net/ directory that holds nothing but a managed index.ts. Any other local entry at those two paths fails the install. See Installation for the setup and removal commands.
The embedded policy snapshot
The published artifact carries a snapshot of your user policy: oneglobalThis.__CC_SAFETY_NET_EMBEDDED_POLICY__ = … assignment appended to the file, with the policy normalized before it is written. At runtime, the snapshot applies only on a machine with no policy file of its own, such as an Orb with an empty home directory. A machine that has a policy file, even an invalid one, keeps its own behavior. Audit retention, user rulebooks, and project-scope policy are not embedded. A policy edit ships only on the next install or update.
Amp reads plugins at startup, so a newly published or removed plugin has no effect on the running session until Amp reloads.
Detecting Amp Code
doctor detects the plugin by parsing amp plugins list output, where a personal-scope plugin renders as ✓ cc-safety-net (User Plugins) <status>. That output carries no version, so doctor never reports version drift for Amp. cc-safety-net update republishes the current artifact regardless.
Amp Code coverage limits
- Amp does not define the execution order of multiple plugins subscribed to the same
tool.callevent. CC Safety Net evaluates the input it receives and cannot re-evaluate an input that another plugin rewrites after CC Safety Net has already allowed it.
Verify the integration
npx cc-safety-net doctor reports the detected integration and config path for every agent, and runs a self-test wherever protection is active. See CLI commands for its options and exit behavior.
For a hook that is not firing for a specific agent, see the per-agent steps in Troubleshooting.
Where to go next
The technical guides run from the user-facing lifecycle down to the reasoning behind the design. This page is step 2.- Back: How it works, which presents the same interception as one tool call from end to end.
- Next: Architecture, which specifies the guard every integration on this page feeds, including the ordered stage table.
- Then: Analysis engine, which explains how a command is classified once it reaches the classifier.
- Finally: Design principles, which explains the integration models and guard order.