shim-cli plugin
This plugin registers shim's local hooks. Claude Code gets the prompt,
verified PreToolUse and PostToolUse, Stop, and SessionEnd events. Codex
gets the prompt event only. VS Code gets the prompt, PreToolUse,
PostToolUse and Stop. There shim never masks, stops a prompt or denies a
call under enforce, and only reports on a result: measured against VS Code
1.137.0, a block after a tool is read straight through by the model.
Three manifests sit side by side, one per format: .claude-plugin/plugin.json,
.codex-plugin/plugin.json, and plugin.json, the Agent Plugins v1 manifest
VS Code, GitHub Copilot CLI and the Copilot app read. Their hook files are
hooks/claude.json, hooks/hooks.json and com.github.copilot/hooks/hooks.json.
The Copilot clients run that last file too, so its command stands down when
COPILOT_CLI is set: shim install copilot is their route, and inspecting a
prompt twice helps nobody.
The plugin carries a self-contained hook in bin/shim.pyz on main and on
every tag, so it needs no package-manager step and no prerequisite beyond
Python 3.9 or newer, which is what a stock macOS provides. Both clients reach it.
The plugin is the hooks, and only the hooks. shim watch, shim report,
and shim config are CLI commands and are not in the archive. The hook is a
cold-start subprocess on every event and must not import command-only code.
Install the package to use those commands; for Claude, adding it does not
duplicate hook registration because the launcher simply prefers the package.
What runs
hooks/run-shim <client> [plugin-root] resolves in this order and stops at
the first that works:
shim-hookonPATH— the package install. Preferred when present: it is the newest build and starts faster, because a zipapp has no bytecode cache and reparses its modules on every event.<plugin-root>/bin/shim.pyz— the bundled archive. The root is the second argument when one is given, elseCLAUDE_PLUGIN_ROOT. Claude sets the variable; Codex sets no such variable, so itshooks.jsonpasses${PLUGIN_ROOT}as the argument.- Nothing runnable — the prompt is allowed and one line is written to stderr explaining why it was not inspected.
Case 3 never blocks. A guard that cannot run is a guard that is off, not a reason to refuse someone's prompt. The same holds for a tool event: an empty stdout leaves the tool call and its result exactly as the client produced them.
shim doctor <client> prints a coverage table of what shim sees and can change
at each event, reports which of the three launchers is live, warns when the
bundled archive and the installed package disagree about their version, and
fails when both this plugin and a shim install hook are active for the same
client — that combination inspects every prompt and every tool event twice.
Choosing an installation method
The marketplace plugin and shim install codex / shim install claude are
alternative hook registrations. Do not use both for the same client. Either
client can add the package for CLI commands without duplicating registration:
the plugin launcher switches to the package hook.
uv tool install --python 3.12 --compile-bytecode shim
# or
pipx install --python python3.12 shim
--python matters on a machine whose only Python is the system 3.9:
without it uv quietly installs the last release that ran there, 0.2.0.
bin/shim.pyz is built by scripts/build_zipapp.py and committed on main and
on every tag. A fork or partial copy without it falls back to PATH or to
case 4.