File: README.md
Context Mode
The other half of the context problem.
The Problem
Every MCP tool call dumps raw data into your context window. A Playwright snapshot costs 56 KB. Twenty GitHub issues cost 59 KB. One access log โ 45 KB. After 30 minutes, 40% of your context is gone. And when the agent compacts the conversation to free space, it forgets which files it was editing, what tasks are in progress, and what you last asked for. On top of that, the agent wastes output tokens on filler, pleasantries, and verbose explanations โ burning context from both sides.
How Context Mode Solves It
Context Mode is an MCP server that solves all four sides of this problem:
Context Saving โ Sandbox tools keep raw data out of the context window. 315 KB becomes 5.4 KB. 98% reduction.
Session Continuity โ Every file edit, git operation, task, error, and user decision is tracked in SQLite. When the conversation compacts, context-mode doesn't dump this data back into context โ it indexes events into FTS5 and retrieves only what's relevant via BM25 search. The model picks up exactly where you left off. If you don't
--continue, previous session data is deleted immediately โ a fresh session means a clean slate.Think in Code โ The LLM should program the analysis, not compute it. Instead of reading 50 files into context to count functions, the agent writes a script that does the counting and
console.log()s only the result. One script replaces ten tool calls and saves 100x context. This is a mandatory paradigm across all 17 supported clients, plus the OpenClaw gateway integration: stop treating the LLM as a data processor, treat it as a code generator.js// Before: 47 ร Read() = 700 KB. After: 1 ร ctx_execute() = 3.6 KB. ctx_execute("javascript", ` const files = fs.readdirSync('src').filter(f => f.endsWith('.ts')); files.forEach(f => console.log(f + ': ' + fs.readFileSync('src/'+f,'utf8').split('\\n').length + ' lines')); `);No prose-style enforcement โ context-mode keeps raw data out of context but never dictates how the model writes its final answer. Brevity, completeness, formatting โ your model's call (or yours via your own
CLAUDE.md/AGENTS.md). Aggressive brevity prompts have been shown to degrade coding/reasoning benchmarks (Moonshot AI onkimi-k2.5) โ the routing block stays focused on where data goes, not on how the model talks.
Install
Platforms are grouped by install complexity. Hook-capable platforms get automatic routing enforcement. Non-hook platforms need a one-time routing file copy.
Claude Code โ plugin marketplace, fully automatic
Prerequisites: Claude Code v1.0.33+ (claude --version). If /plugin is not recognized, update first: brew upgrade claude-code or npm update -g @anthropic-ai/claude-code.
Install:
/plugin marketplace add mksglu/context-mode
/plugin install context-mode@context-modeRestart Claude Code (or run /reload-plugins).
Verify:
/context-mode:ctx-doctorAll checks should show [x]. The doctor validates runtimes, hooks, FTS5, and plugin registration.
Routing: Automatic. The SessionStart hook injects routing instructions at runtime โ no file is written to your project. The plugin registers all hooks (PreToolUse, PostToolUse, UserPromptSubmit, PreCompact, SessionStart, Stop) and 11 MCP tools โ six sandbox tools (ctx_batch_execute, ctx_execute, ctx_execute_file, ctx_index, ctx_search, ctx_fetch_and_index) plus five meta-tools (ctx_stats, ctx_doctor, ctx_upgrade, ctx_purge, ctx_insight).
| Slash Command | What it does |
|---|---|
/context-mode:ctx-stats |
Context savings โ per-tool breakdown, tokens consumed, savings ratio. |
/context-mode:ctx-doctor |
Diagnostics โ runtimes, hooks, FTS5, plugin registration, versions. |
/context-mode:ctx-index |
Index a local file or directory into the persistent FTS5 knowledge base. |
/context-mode:ctx-search |
Search previously indexed content. |
/context-mode:ctx-upgrade |
Pull latest, rebuild, migrate cache, fix hooks. |
/context-mode:ctx-purge |
Permanently delete all indexed content from the knowledge base. |
/context-mode:ctx-insight |
Opens the hosted Insight dashboard (context-mode.com/insight) in your browser โ org analytics for AI-assisted engineering teams. |
Note: Slash commands are a Claude Code plugin feature. On other platforms, type
ctx stats,ctx doctor,ctx index,ctx search,ctx upgrade, orctx insightin the chat โ the model calls the MCP tool automatically. See Utility Commands.
Status line (optional): Claude Code's plugin manifest cannot declare a status line, so this is a one-time manual edit to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "context-mode statusline"
}
}After saving, restart Claude Code. The bar shows $ saved this session ยท $ saved across sessions ยท % efficient so you can see savings accumulate in real time. The wiring is path-free โ context-mode statusline resolves through the bundled CLI regardless of where the plugin cache lives.
Alternative โ MCP-only install (no hooks or slash commands)
claude mcp add context-mode -- npx -y context-modeThis gives you all 11 MCP tools without automatic routing. The model can still use them โ it just won't be nudged to prefer them over raw Bash/Read/WebFetch. Good for trying it out before committing to the full plugin.
Gemini CLI โ one config file, hooks included
Prerequisites: Node.js >= 22.5 (or Bun), Gemini CLI installed.
Install:
Install context-mode globally:
bashnpm install context-modeAdd the following to
~/.gemini/settings.json. This single file registers the MCP server and all four hooks:json{ "mcpServers": { "context-mode": { "command": "context-mode" } }, "hooks": { "BeforeTool": [ { "matcher": "run_shell_command|read_file|read_many_files|grep_search|search_file_content|web_fetch|activate_skill|mcp__plugin_context-mode|mcp__context-mode|mcp__(?!.*context-mode)", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli beforetool" }] } ], "AfterTool": [ { "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli aftertool" }] } ], "PreCompress": [ { "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli precompress" }] } ], "SessionStart": [ { "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook gemini-cli sessionstart" }] } ] } }Restart Gemini CLI.
Verify:
/mcp listYou should see context-mode: ... - Connected.
Routing: Automatic via SessionStart hook. Optionally copy routing instructions for full model awareness:
cp node_modules/context-mode/configs/gemini-cli/GEMINI.md ./GEMINI.mdWhy the BeforeTool matcher? It targets only tools that produce large output (
run_shell_command,read_file,read_many_files,grep_search,search_file_content,web_fetch,activate_skill) plus context-mode's own tools (mcp__plugin_context-mode). This avoids unnecessary hook overhead on lightweight tools while intercepting every tool that could flood your context window.
Full config reference: configs/gemini-cli/settings.json
VS Code Copilot โ hooks with SessionStart
Prerequisites: Node.js >= 22.5 (or Bun), VS Code with Copilot Chat v0.32+.
Install:
Install context-mode globally:
bashnpm install context-modeCreate
.vscode/mcp.jsonin your project root:json{ "servers": { "context-mode": { "command": "context-mode" } } }Create
.github/hooks/context-mode.json:json{ "hooks": { "PreToolUse": [ { "type": "command", "command": "context-mode hook vscode-copilot pretooluse" } ], "PostToolUse": [ { "type": "command", "command": "context-mode hook vscode-copilot posttooluse" } ], "SessionStart": [ { "type": "command", "command": "context-mode hook vscode-copilot sessionstart" } ] } }Restart VS Code.
Verify: Open Copilot Chat and type ctx stats. Context-mode tools should appear and respond.
Routing: Automatic via SessionStart hook. Optionally copy routing instructions for full model awareness:
cp node_modules/context-mode/configs/vscode-copilot/copilot-instructions.md .github/copilot-instructions.mdFull hook config including PreCompact: configs/vscode-copilot/hooks.json
JetBrains Copilot โ hooks with SessionStart
Prerequisites: Node.js >= 22.5 (or Bun), JetBrains IDE with GitHub Copilot plugin v1.5.57+.
Install:
Install context-mode globally:
bashnpm install context-modeAdd MCP server via Settings UI: Settings > Tools > AI Assistant > Model Context Protocol (MCP) > Add Server:
- Name:
context-mode - Command:
context-mode
- Name:
Create
.github/hooks/context-mode.json:json{ "hooks": { "PreToolUse": [ { "type": "command", "command": "context-mode hook jetbrains-copilot pretooluse" } ], "PostToolUse": [ { "type": "command", "command": "context-mode hook jetbrains-copilot posttooluse" } ], "SessionStart": [ { "type": "command", "command": "context-mode hook jetbrains-copilot sessionstart" } ] } }Restart the JetBrains IDE.
Verify: Open Copilot Chat and type ctx stats. Context-mode tools should appear and respond.
Routing: Automatic via SessionStart hook. Optionally copy routing instructions for full model awareness:
cp node_modules/context-mode/configs/jetbrains-copilot/copilot-instructions.md .github/copilot-instructions.mdFull hook config including PreCompact: configs/jetbrains-copilot/hooks.json
Full setup guide: docs/jetbrains-copilot.md
GitHub Copilot CLI โ MCP + hooks
Prerequisites: Node.js >= 22.5 (or Bun), GitHub Copilot CLI (copilot) installed. Set COPILOT_HOME first if you use an isolated Copilot home.
Install โ Option A (plugin, one command โ recommended):
npm install context-mode # the plugin's MCP server runs the global binary
copilot plugin install mksglu/context-mode:configs/copilot-cli # registers MCP + hooks + routing skillThe bundle's .mcp.json pins CONTEXT_MODE_PLATFORM=copilot-cli, so context-mode self-identifies as Copilot โ ctx_upgrade and platform detection resolve copilot-cli even when Claude Code is co-installed (whose ~/.claude/ would otherwise win). No context-mode upgrade / agent call needed. To try it from a local clone before it lands on the default branch, point Copilot at the bundle directory: copilot --plugin-dir /path/to/context-mode/configs/copilot-cli.
Install โ Option B (manual, no plugin):
Install context-mode globally:
bashnpm install context-modeRegister the MCP server with Copilot CLI's built-in command (writes
~/.copilot/mcp-config.jsonfor you):bashcopilot mcp add context-mode -- context-modeConfigure hooks in
~/.copilot/hooks/context-mode.json(or$COPILOT_HOME/hooks/context-mode.json). The config uses flat{ "type": "command", "command": "..." }entries; context-mode also writes a top-level"version": 1, but that field is optional โ the Copilot CLI accepts hook configs that omit it (it is pinned only for self-documentation). Copilot CLI fires six events context-mode uses:json{ "version": 1, "hooks": { "preToolUse": [{ "type": "command", "command": "context-mode hook copilot-cli pretooluse" }], "postToolUse": [{ "type": "command", "command": "context-mode hook copilot-cli posttooluse" }], "preCompact": [{ "type": "command", "command": "context-mode hook copilot-cli precompact" }], "sessionStart": [{ "type": "command", "command": "context-mode hook copilot-cli sessionstart" }], "userPromptSubmitted": [{ "type": "command", "command": "context-mode hook copilot-cli userpromptsubmit" }], "agentStop": [{ "type": "command", "command": "context-mode hook copilot-cli stop" }] } }Or let context-mode write this hooks file for you:
context-mode upgrade(run from a Copilot CLI context, or withCONTEXT_MODE_PLATFORM=copilot-cli).upgradewrites the hooks file only โ register the MCP server withcopilot mcp addin step 2.Restart Copilot CLI.
Plugins: Option A above uses Copilot CLI's plugin system, which registers MCP servers (
.mcp.json), hooks (hooks.json), and skills (skills/) together โ not just skills/agents. The shipped bundle isconfigs/copilot-cli/;copilot plugin install owner/repo:pathinstalls it in one command (no clone). Option B is the equivalent without a plugin.
Version note: the hook commands run the global
context-mode(context-mode hook copilot-cli โฆ), so they need a context-mode version with Copilot CLI support. On an older global the hooks are inert (no routing/capture) until you upgrade โ but they do not block your tools (context-mode fails open). Upgrade withnpm install context-mode@latest.
Verify: In a Copilot CLI session, type ctx stats. Context-mode tools should appear and respond. Run context-mode doctor to confirm hook + MCP registration.
Routing: Automatic via hooks (PreToolUse interception + SessionStart routing block). Auto-detected via MCP clientInfo.name (GitHub Copilot CLI) or, in a bare shell, a context-mode-written marker (~/.copilot/mcp-config.json or ~/.copilot/hooks/context-mode.json) โ not a bare ~/.copilot/ dir, so a co-installed-but-unconfigured Copilot CLI is not mis-detected as context-mode-on-copilot.
See docs/platform-support.md for the full reference. Tracking: #775.
Cursor โ hooks with stop support
Prerequisites: Node.js >= 22.5 (or Bun), Cursor with agent mode.
๐ง Work in progress โ the Marketplace plugin is awaiting Cursor team review. Until it's listed, install via the local-folder path described in Option A. Tracking in #485 / #489.
Option A โ Marketplace plugin (recommended once published)
After Cursor lists context-mode in the Marketplace, install with one click. The plugin auto-registers MCP, hooks (preToolUse, postToolUse, sessionStart, stop, afterAgentResponse), rules, and skills. No manual config required.
Until then, use the local-folder path:
Windows (PowerShell) โ Cursor does not follow Windows symlinks/junctions, so use robocopy:
git clone https://github.com/mksglu/context-mode.git
cd context-mode
robocopy . "$env:USERPROFILE\.cursor\plugins\local\context-mode" /MIR `
/XD node_modules .git build web tests scripts .vscode `
/XF *.log .gitignore *.bundle.mjs.mapmacOS / Linux:
git clone https://github.com/mksglu/context-mode.git
ln -s "$PWD/context-mode" ~/.cursor/plugins/local/context-modeRestart Cursor. The plugin appears in Settings โ Plugins as "Context Mode (Local)". To pull updates, re-run the same robocopy / ln -s line.
Note: if
.cursor/hooks.jsonalready contains context-mode entries from a priorOption Binstall,context-mode doctorwill warn about duplicate hook firings. Remove one configuration to keep events single-fire.
Option B โ Manual install (existing path)
Install context-mode globally:
bashnpm install context-modeCreate
.cursor/mcp.jsonin your project root (or~/.cursor/mcp.jsonfor global):json{ "mcpServers": { "context-mode": { "command": "context-mode" } } }Create
.cursor/hooks.json(or~/.cursor/hooks.jsonfor global):json{ "version": 1, "hooks": { "preToolUse": [ { "command": "context-mode hook cursor pretooluse", "matcher": "Shell|Read|Grep|WebFetch|Task|MCP:ctx_execute|MCP:ctx_execute_file|MCP:ctx_batch_execute" } ], "postToolUse": [ { "command": "context-mode hook cursor posttooluse" } ], "stop": [ { "command": "context-mode hook cursor stop" } ] } }The
preToolUsematcher is optional โ without it, the hook fires on all tools. Thestophook fires when the agent turn ends and can send a followup message to continue the loop.afterAgentResponseis also available (fire-and-forget, receives full response text).Copy the routing rules file. Cursor lacks a SessionStart hook, so the model needs a rules file for routing awareness:
bashmkdir -p .cursor/rules cp node_modules/context-mode/configs/cursor/context-mode.mdc .cursor/rules/context-mode.mdcRestart Cursor or open a new agent session.
Verify: Open Cursor Settings > MCP and confirm "context-mode" shows as connected. In agent chat, type ctx stats.
Routing: Hooks enforce routing programmatically via preToolUse/postToolUse/stop. The .cursor/rules/context-mode.mdc file provides routing instructions at session start since Cursor's sessionStart hook is currently rejected by their validator (forum report). Project .cursor/hooks.json overrides ~/.cursor/hooks.json.
Known limitation: Cursor accepts additional_context in hook responses but does not surface it to the model (forum #155689). Routing relies on the .mdc rules file instead of hook context injection.
Full configs: configs/cursor/hooks.json | configs/cursor/mcp.json | configs/cursor/context-mode.mdc
OpenCode โ TypeScript plugin with hooks
Prerequisites: Node.js >= 22.5 (or Bun), OpenCode installed.
Install:
Add to
opencode.jsonin your project root (or~/.config/opencode/opencode.jsonfor global):json{ "$schema": "https://opencode.ai/config.json", "plugin": ["context-mode"] }The
pluginentry registers all 11ctx_*tools natively and enables hooks โ OpenCode calls context-mode's TypeScript plugin in-process, so there is no redundant stdio MCP child per session.(Optional) Copy the routing rules file. The model needs an
AGENTS.mdfile for routing awareness:bashcp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.mdThis tells the model which tools to use and which commands are blocked. Without it, hooks still enforce routing โ but the model won't know why a command was denied.
Restart OpenCode.
Verify: In the OpenCode session, type ctx stats. Context-mode tools should appear and respond.
Upgrade note: If an existing config has BOTH plugin: ["context-mode"] AND mcp.context-mode, OpenCode will register zero ctx_* tools โ the plugin path correctly suppresses MCP duplicates, but the legacy MCP entry confuses the loader. Run context-mode upgrade to remove the legacy mcp.context-mode entry; your other MCP servers are preserved. v1.0.140+ emits a stderr diagnostic with the same guidance when this happens.
Routing: Hooks enforce routing programmatically via tool.execute.before and tool.execute.after. The optional AGENTS.md file provides routing instructions for model awareness. The experimental.session.compacting hook builds resume snapshots when the conversation compacts. The experimental.chat.system.transform hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The chat.message hook captures user prompts and decisions (UserPromptSubmit equivalent).
Note: OpenCode lacks a real SessionStart hook (#14808, #5409). The plugin uses
experimental.chat.system.transformas a surrogate โ it injects both the routing block and resume snapshots into the system prompt. User-prompt capture useschat.messageinstead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
Full configs: configs/opencode/opencode.json | configs/opencode/AGENTS.md
KiloCode โ TypeScript plugin with hooks
Prerequisites: Node.js >= 22.5 (or Bun), KiloCode installed.
Install:
Add to
kilo.jsonin your project root (or~/.config/kilo/kilo.jsonfor global):json{ "$schema": "https://app.kilo.ai/config.json", "plugin": ["context-mode"] }The
pluginentry registers all 11ctx_*tools natively and enables hooks โ KiloCode calls context-mode's TypeScript plugin in-process, so there is no redundant stdio MCP child per session.(Optional) Copy the routing rules file. KiloCode shares the OpenCode plugin architecture, so the model needs an
AGENTS.mdfile for routing awareness:bashcp node_modules/context-mode/configs/opencode/AGENTS.md AGENTS.mdRestart KiloCode.
Verify: In the KiloCode session, type ctx stats. Context-mode tools should appear and respond.
Upgrade note: If an existing config has BOTH plugin: ["context-mode"] AND mcp.context-mode, KiloCode will register zero ctx_* tools โ the plugin path correctly suppresses MCP duplicates, but the legacy MCP entry confuses the loader. Run context-mode upgrade to remove the legacy mcp.context-mode entry; your other MCP servers are preserved. v1.0.140+ emits a stderr diagnostic with the same guidance when this happens.
Routing: Hooks enforce routing programmatically via tool.execute.before and tool.execute.after. The optional AGENTS.md file provides routing instructions for model awareness. The experimental.session.compacting hook builds resume snapshots when the conversation compacts. The experimental.chat.system.transform hook injects the routing block and prior-session snapshots at session start, enabling session continuity across restarts. The chat.message hook captures user prompts and decisions (UserPromptSubmit equivalent).
Note: KiloCode shares the same plugin architecture as OpenCode, using the OpenCodeAdapter with platform-specific configuration paths (
kilo.jsoninstead ofopencode.json,~/.config/kilo/instead of~/.config/opencode/). Like OpenCode, it lacks a real SessionStart hook โ the plugin usesexperimental.chat.system.transformas a surrogate. User-prompt capture useschat.messageinstead of the missing UserPromptSubmit hook. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire per project.
OpenClaw / Pi Agent โ native gateway plugin
Prerequisites: OpenClaw gateway running (>2026.1.29), Node.js 22+.
context-mode runs as a native OpenClaw gateway plugin, targeting Pi Agent sessions (Read/Write/Edit/Bash tools). Unlike other platforms, there's no separate MCP server โ the plugin registers directly into the gateway runtime via OpenClaw's plugin API.
Install:
Clone and install:
bashgit clone https://github.com/mksglu/context-mode.git cd context-mode npm run install:openclawThe installer uses
$OPENCLAW_STATE_DIRfrom your environment (default:/openclaw). To specify a custom path:bashnpm run install:openclaw -- /path/to/openclaw-stateCommon locations: Docker โ
/openclaw(the default). Local โ~/.openclawor wherever you setOPENCLAW_STATE_DIR.The installer handles everything:
npm install,npm run build,better-sqlite3native rebuild, extension registration inruntime.json, and gateway restart via SIGUSR1.Open a Pi Agent session.
Verify: The plugin registers 8 hooks via api.on() (lifecycle) and api.registerHook() (commands). Type ctx stats to confirm tools are loaded.
Routing: Automatic. All tool interception, session tracking, and compaction recovery hooks activate automatically โ no manual hook configuration or routing file needed.
Minimum version: OpenClaw >2026.1.29 โ this includes the
api.on()lifecycle fix from PR #9761. On older versions, lifecycle hooks silently fail. The adapter falls back to DB snapshot reconstruction (less precise but preserves critical state).
Full documentation: docs/adapters/openclaw.md
Codex CLI โ MCP + hooks
Prerequisites: Node.js >= 22.5 (or Bun), Codex CLI installed.
Install:
Add the context-mode marketplace and install the plugin from Codex's plugin UI:
bashcodex plugin marketplace add mksglu/context-modeEnable plugin-provided hooks while the Codex feature is still gated:
toml[features] plugin_hooks = true hooks = trueFeature flag note: Current Codex builds expose hooks under
[features].hooks
(orcodex --enable hooks). Prefer[features].hooks;[features].codex_hooks
remains accepted as a legacy alias in current Codex builds. Bundled plugin hooks
additionally requireplugin_hooksuntil Codex enables plugin hooks by default.Custom storage location: if Codex cannot write the adapter default storage directory, set
CONTEXT_MODE_DIRto an absolute writable root in the environment that launches Codex. Sessions
and stats use<root>/sessions; indexed content uses<root>/content.bashCONTEXT_MODE_DIR="$HOME/.codex-context-mode" codexRestart Codex CLI and verify MCP with
ctx stats.ctx statsproves the plugin MCP server is installed and reachable; it does
not prove hooks are trusted or running.Review and trust the context-mode plugin hooks if Codex prompts for hook
approval. Plugin hooks are only active after both feature flags are enabled
and Codex has accepted the hook commands.
The Codex plugin manifest provides MCP via .codex-plugin/mcp.json, skills viaskills/, and bundled hooks via .codex-plugin/hooks.json. No manual[mcp_servers.context-mode] block or $CODEX_HOME/hooks.json is needed whenplugin_hooks is enabled and the plugin hooks are trusted.
Node/PATH note: context-mode still needs
nodevisible to the Codex process.
The plugin removes manual Codex config, but it does not vendor Node or inherit
login-shell PATH fixes automatically.
Manual fallback for Codex builds without plugin_hooks:
Install context-mode globally:
bashnpm install context-modeAdd to
~/.codex/config.toml:toml[features] hooks = true [mcp_servers.context-mode] command = "context-mode" [mcp_servers.context-mode.env] CONTEXT_MODE_PLATFORM = "codex"Create
$CODEX_HOME/hooks.json(or~/.codex/hooks.jsonwhenCODEX_HOMEis unset):json{ "hooks": { "PreToolUse": [{ "matcher": "local_shell|shell|shell_command|exec_command|Bash|Shell|apply_patch|Edit|Write|grep_files|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__", "hooks": [{ "type": "command", "command": "context-mode hook codex pretooluse" }] }], "PostToolUse": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex posttooluse" }] }], "SessionStart": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex sessionstart" }] }], "PreCompact": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex precompact" }] }], "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex userpromptsubmit" }] }], "Stop": [{ "hooks": [{ "type": "command", "command": "context-mode hook codex stop" }] }] } }PreToolUseenforces deny/block routing today and is prepared for input rewrites once Codex supports them.PostToolUsecaptures session events.PreCompactbuilds the resume snapshot before compaction.SessionStartrestores state after compaction.UserPromptSubmitcaptures user decisions and corrections.Stoprecords turn-end state.Note: Codex PreToolUse routing currently supports deny rules only (blocks dangerous commands). It still needs upstream
updatedInputsupport before context-mode can rewrite tool input; track openai/codex#18491. Context injection (additionalContext) is not supported in Codex PreToolUse โ it works via PostToolUse and SessionStart instead. This is handled automatically.PreCompactsupport is runtime-gated: it is present in Codex CLI 0.130.0, while the public Codex hooks docs may lag the shipped hook-event list. Older Codex builds that do not emitPreCompactwill not create pre-compaction snapshots.Copy routing instructions (recommended even with hooks for full routing awareness):
bashCM_ROOT="$(npm root -g)/context-mode" cp "$CM_ROOT/configs/codex/AGENTS.md" ./AGENTS.mdFor global use:
CM_ROOT="$(npm root -g)/context-mode"; cp "$CM_ROOT/configs/codex/AGENTS.md" ~/.codex/AGENTS.md. Global applies to all projects. If both exist, Codex CLI merges them.Restart Codex CLI.
Verify: Start a session and type ctx stats to verify MCP. To verify hook routing, confirm Codex lists/trusts the context-mode plugin hooks, then run a command that matches the routing rules.
Routing: MCP tools work after plugin install. Plugin hook routing is active only when hooks and plugin_hooks are enabled and Codex trusts the plugin hook commands. Manual hook routing is active when $CODEX_HOME/hooks.json or ~/.codex/hooks.json is configured. The AGENTS.md file provides routing instructions for model awareness.
Kimi Code โ MCP + hooks (TOML config, same JSON wire protocol as Codex)
Prerequisites: Node.js >= 22.5 (or Bun), Kimi Code CLI installed.
Install context-mode:
bashnpm install context-modeAdd context-mode as an MCP server. Add to
~/.kimi-code/mcp.json:json{ "mcpServers": { "context-mode": { "command": "context-mode", "args": [] } } }Add hooks to
~/.kimi-code/config.toml:toml[[hooks]] event = "PreToolUse" matcher = "Bash|Shell|Read|Edit|Write|WebFetch|Agent|ctx_execute|ctx_execute_file|ctx_batch_execute|ctx_fetch_and_index|ctx_search|ctx_index|mcp__" command = "context-mode hook kimi pretooluse" timeout = 30 [[hooks]] event = "PostToolUse" command = "context-mode hook kimi posttooluse" timeout = 30 [[hooks]] event = "SessionStart" command = "context-mode hook kimi sessionstart" timeout = 30 [[hooks]] event = "PreCompact" command = "context-mode hook kimi precompact" timeout = 30 [[hooks]] event = "UserPromptSubmit" command = "context-mode hook kimi userpromptsubmit" timeout = 30 [[hooks]] event = "Stop" command = "context-mode hook kimi stop" timeout = 30Restart Kimi Code CLI and verify MCP with
ctx stats.Note: Kimi Code uses the same JSON stdin/stdout wire protocol as Codex, but accepts
additionalContext,updatedInput, andpermissionDecision: "ask"in PreToolUse responses (Codex rejects these). The kimi hook normalizesContentPart[]prompt arrays to strings for downstream extractors.(Optional) Copy the routing instructions file for your project:
bashcp "$(npm root -g)/context-mode/configs/codex/AGENTS.md" ./AGENTS.mdOr for global use:
bashCM_ROOT="$(npm root -g)/context-mode"; cp "$CM_ROOT/configs/codex/AGENTS.md" ~/.kimi-code/AGENTS.md
Full documentation: docs/adapters/kimi-code.md
Qwen Code โ MCP + hooks (identical wire protocol to Claude Code)
Prerequisites: Node.js >= 22.5 (or Bun), Qwen Code installed (npm install @qwen-code/qwen-code).
Install context-mode:
bashnpm install context-modeAdd context-mode as an MCP server. Add to
~/.qwen/settings.json:json{ "mcpServers": { "context-mode": { "command": "context-mode", "args": [] } } }Add hooks for routing enforcement and session tracking. Add to
~/.qwen/settings.json:json{ "hooks": { "PreToolUse": [{ "matcher": "run_shell_command|read_file|read_many_files|grep_search|web_fetch|agent|mcp__plugin_context-mode_context-mode__ctx_execute|mcp__plugin_context-mode_context-mode__ctx_execute_file|mcp__plugin_context-mode_context-mode__ctx_batch_execute|mcp__(?!.*context-mode)", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code pretooluse" }] }], "PostToolUse": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code posttooluse" }] }], "SessionStart": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code sessionstart" }] }], "PreCompact": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code precompact" }] }], "UserPromptSubmit": [{ "matcher": "", "hooks": [{ "type": "command", "command": "context-mode hook qwen-code userpromptsubmit" }] }] } }Copy routing instructions (recommended for full routing awareness):
bashcp node_modules/context-mode/configs/qwen-code/QWEN.md ./QWEN.mdFor global use:
cp node_modules/context-mode/configs/qwen-code/QWEN.md ~/.qwen/QWEN.mdRestart Qwen Code.
Verify: Start a session and type ctx stats. Context-mode tools should appear and respond.
Note: Qwen Code uses the same hook wire protocol as Claude Code (JSON stdin/stdout, same event names). Auto-detected via MCP clientInfo (qwen-cli-mcp-client-*) or QWEN_PROJECT_DIR env var.
Antigravity IDE โ MCP-only, no hooks
This is the Antigravity desktop IDE. For the
agycommand-line tool, see Antigravity CLI (agy) below โ it installs as a full plugin with hooks.
Prerequisites: Node.js >= 22.5 (or Bun), the Antigravity IDE installed.
Install:
Install context-mode globally:
bashnpm install context-modeAdd to
~/.gemini/antigravity/mcp_config.json:json{ "mcpServers": { "context-mode": { "command": "context-mode" } } }Copy routing instructions (Antigravity has no hook support):
bashcp node_modules/context-mode/configs/antigravity/GEMINI.md ./GEMINI.mdRestart Antigravity.
Verify: In an Antigravity session, type ctx stats. Context-mode tools should appear and respond.
Routing: Manual. The GEMINI.md file is the only enforcement method (~60% compliance). There is no programmatic interception. Auto-detected via MCP protocol handshake (clientInfo.name) โ no manual platform configuration needed.
Full configs: configs/antigravity/mcp_config.json | configs/antigravity/GEMINI.md
Antigravity CLI (agy) โ plugin (MCP + skill + hooks)
The
agycommand-line tool, not the Antigravity desktop IDE above.
Prerequisites: Node.js >= 22.5 (or Bun), Antigravity CLI (agy) โฅ 1.0.7 (agy update to upgrade). Verified on agy 1.0.10.
Install:
npm install context-mode # the plugin's MCP server + hooks run the global binary
agy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli # registers MCP + rule + skill + hooksRestart agy.
MCP-only (no plugin, no hooks): if you only want the ctx_* tools, skip the plugin and add context-mode to agy's global MCP profile ~/.gemini/config/mcp_config.json (distinct from the Antigravity IDE's ~/.gemini/antigravity/ path), then restart agy:
{ "mcpServers": { "context-mode": { "command": "context-mode" } } }Verify: type ctx stats in an agy session, or run any prompt from Try It and check the savings. context-mode doctor confirms MCP + hook registration. Remove with agy plugin uninstall context-mode.
Routing: the routing rule and skill provide the instruction layer; bounded PreToolUse blocks high-flood tools and PostToolUse captures sessions. The bundle pins CONTEXT_MODE_PLATFORM=antigravity-cli so agy is detected even when Claude Code is co-installed (#774).
Kiro โ hooks with steering file
Prerequisites: Node.js >= 22.5 (or Bun), Kiro with MCP enabled (Settings > search "MCP").
Install:
Install context-mode globally:
bashnpm install context-modeAdd to
.kiro/settings/mcp.jsonin your project (or~/.kiro/settings/mcp.jsonfor global):json{ "mcpServers": { "context-mode": { "command": "context-mode" } } }Create
.kiro/hooks/context-mode.json:json{ "name": "context-mode", "description": "Context-mode hooks for context window protection", "hooks": { "preToolUse": [ { "matcher": "execute_bash|fs_read|@context-mode/ctx_execute|@context-mode/ctx_execute_file|@context-mode/ctx_batch_execute|@(?!context-mode/)", "command": "context-mode hook kiro pretooluse" } ], "postToolUse": [ { "matcher": "*", "command": "context-mode hook kiro posttooluse" } ] } }Copy routing instructions. Kiro's
agentSpawn(SessionStart) is not yet implemented, so the model needs a routing file at session start:bashcp node_modules/context-mode/configs/kiro/KIRO.md ./KIRO.mdRestart Kiro.
Verify: Open the Kiro panel > MCP Servers tab and confirm "context-mode" shows a green status indicator. In chat, type ctx stats.
Routing: Hooks enforce routing programmatically via preToolUse/postToolUse. The KIRO.md file provides routing instructions since agentSpawn (SessionStart equivalent) is not yet wired. Tool names appear as @context-mode/ctx_batch_execute, @context-mode/ctx_search, etc. Auto-detected via MCP protocol handshake.
Full configs: configs/kiro/mcp.json | configs/kiro/agent.json | configs/kiro/KIRO.md
Zed โ MCP-only, no hooks
Prerequisites: Node.js >= 22.5 (or Bun), Zed installed.
Install:
Install context-mode globally:
bashnpm install context-modeAdd to
~/.config/zed/settings.json(Windows:%APPDATA%\Zed\settings.json):json{ "context_servers": { "context-mode": { "command": "context-mode", "args": [], "env": {} } } }Note: Zed uses
"context_servers"instead of"mcpServers".argsandenvare optional for context-mode, but are shown here to match Zed's custom MCP server shape.Copy routing instructions (Zed has no hook support):
bashcp node_modules/context-mode/configs/zed/AGENTS.md ./AGENTS.mdRestart Zed (or save
settings.jsonโ Zed auto-restarts context servers on config change).
Verify: Open the Agent Panel (Cmd+Shift+A), go to settings, and check the indicator dot next to "context-mode" โ green means active. Type ctx stats in the agent chat.
Routing: Manual. The AGENTS.md file is the only enforcement method (~60% compliance). There is no programmatic interception. Tool names appear as mcp:context-mode:ctx_batch_execute, mcp:context-mode:ctx_search, etc. Auto-detected via MCP protocol handshake.
Pi Coding Agent โ extension with full hook support
Prerequisites: Node.js >= 22.5 (or Bun), Pi Coding Agent installed.
Install:
Install context-mode globally:
bashnpm install context-modeInstall the package into Pi:
bashpi install npm:context-modeAlternative โ add it manually to
~/.pi/agent/settings.json(or.pi/settings.jsonfor project-level):json{ "packages": ["npm:context-mode"] }Add to
~/.pi/agent/mcp.json(or.pi/mcp.jsonfor project-level):json{ "mcpServers": { "context-mode": { "command": "context-mode" } } }Restart Pi.
Verify: In a Pi session, type ctx stats. Context-mode tools should appear and respond.
Routing: Automatic. The extension registers all key lifecycle events (tool_call, tool_result, session_start, session_before_compact), providing full session continuity and routing enforcement.
OMP (Oh My Pi) โ plugin with full hook support
Prerequisites: Node.js >= 22.5 (or Bun), Oh My Pi installed.
Install โ plugin path (recommended):
Run the OMP plugin install:
bashomp plugin install context-modeRestart OMP.
Verify:
bashomp plugin list omp plugin doctorBoth should show
context-modeasenabled.The plugin self-registers its MCP server in
~/.omp/agent/mcp.jsonon first load (spawned asnode /server.bundle.mjs, since the plugin-install package directory is not onPATH), so the 11ctx_*tools become reachable after the restart in step 2 โ no manualmcp.jsonedit needed (#677). An existingcontext-modeentry is never overwritten; remove it if you want the plugin to re-register the bundled path.
Install โ manual plugin path (if omp plugin install is unavailable):
OMP loads anything listed under ~/.omp/plugins/package.json dependencies whose own package.json carries an omp (or pi) field. New plugins default to enabled โ the lock file at ~/.omp/plugins/omp-plugins.lock.json is only consulted when a plugin needs to be explicitly disabled (loader skips runtimeState && !runtimeState.enabled per extensibility/plugins/loader.ts:89-94). So the manual install is two commands:
cd ~/.omp/plugins
bun add context-mode # or: npm install context-modeThen restart OMP. No lock file edit, no version pin โ version is read from the freshly-installed package each time the loader runs (see loader.ts:87 manifest.version = pluginPkg.version).
Install โ MCP-only path (no plugin):
Install context-mode globally:
bashnpm install context-modeAdd to
~/.omp/agent/mcp.json(user scope) or/.omp/mcp.json(project scope):json{ "mcpServers": { "context-mode": { "command": "context-mode" } } }Copy routing instructions:
bashcp node_modules/context-mode/configs/omp/SYSTEM.md ~/.omp/agent/SYSTEM.mdProject-scoped alternative:
cp ... .omp/SYSTEM.md. OMP also auto-discovers anyAGENTS.mdin the project tree.Restart OMP.
Verify (any path): In an OMP session, type ctx stats. Context-mode tools should appear and respond.
Routing: Plugin path โ programmatic enforcement via four pi.on(...) handlers (tool_call returns { block: true, reason } for curl/wget/inline-fetch per upstream hooks/types.ts:566, tool_result captures session events, session_start initializes the per-session DB row, session_before_compact persists a resume snapshot). ~98% compliance, parity with Claude Code hooks. MCP-only path โ rule-based via SYSTEM.md, ~60% compliance. Auto-detected via PI_CODING_AGENT_DIR env var or presence of ~/.omp/. Storage roots at ~/.omp/context-mode/ so OMP and Pi installs never share session DBs, content indices, or stats files.
Full configs: configs/omp/mcp.json | configs/omp/SYSTEM.md | plugin source: src/adapters/omp/plugin.ts
Build Prerequisites (CentOS, RHEL, Alpine)
Context Mode uses better-sqlite3 on Node.js, which ships prebuilt native binaries for most platforms. On glibc >= 2.31 systems (Ubuntu 20.04+, Debian 11+, Fedora 34+, macOS, Windows), npm install works without any build tools.
Linux + Node.js >= 22.5: Context Mode automatically uses the built-in node:sqlite module instead of better-sqlite3. This eliminates the native addon entirely, avoiding sporadic SIGSEGV crashes caused by V8's madvise(MADV_DONTNEED) corrupting the addon's .got.plt section on Linux. No configuration needed โ detection is automatic. Linux + Node < 22.5 is unsupported (#564) โ npm install will fail with remediation instructions.
Bun users: No native compilation needed. Context Mode automatically detects Bun and uses the built-in bun:sqlite module via a compatibility adapter. better-sqlite3 and all its build dependencies are skipped entirely.
On older glibc systems (CentOS 7/8, RHEL 8, Debian 10), prebuilt binaries don't load and better-sqlite3 automatically falls back to compiling from source via prebuild-install || node-gyp rebuild --release. This requires a C++20 compiler (GCC 10+), Make, and Python with setuptools.
Windows / missing binding self-heal: if better_sqlite3.node ends up missing after install (e.g. prebuild-install not on cmd.exe PATH, no MSVC toolchain), the postinstall script and the runtime hook automatically re-fetch the prebuild and repair the binding โ no manual npm rebuild needed (#408).
CentOS 8 / RHEL 8 (glibc 2.28):
dnf install -y gcc-toolset-10-gcc gcc-toolset-10-gcc-c++ make python3 python3-setuptools
scl enable gcc-toolset-10 'npm install context-mode'CentOS 7 / RHEL 7 (glibc 2.17):
yum install -y centos-release-scl
yum install -y devtoolset-10-gcc devtoolset-10-gcc-c++ make python3
pip3 install setuptools
scl enable devtoolset-10 'npm install context-mode'Alpine Linux:
Alpine prebuilt binaries (musl) are available in better-sqlite3 v12.8.0+. With the ^12.6.2 dependency range, npm install resolves to the latest 12.x and works without build tools on Alpine. If you pin an older version:
apk add build-base python3 py3-setuptools
npm install context-modeTools
| Tool | What it does | Context saved |
|---|---|---|
ctx_batch_execute |
Run multiple commands + search multiple queries in ONE call. Opt-in concurrency: 1-8 for I/O-bound batches. |
986 KB โ 62 KB |
ctx_execute |
Run code in 12 languages. Only stdout enters context. | 56 KB โ 299 B |
ctx_execute_file |
Process files in sandbox. Raw content never leaves. | 45 KB โ 155 B |
ctx_index |
Chunk markdown into FTS5 with BM25 ranking. | 60 KB โ 40 B |
ctx_search |
Query indexed content with multiple queries in one call. | On-demand retrieval |
ctx_fetch_and_index |
Fetch URL, chunk and index. Cache reuses content within TTL (default 24h, override per-call with ttl: <ms>). ttl: 0 or force: true to bypass. Pass requests: [{url, source}, ...] + concurrency: 1-8 for parallel multi-URL. |
60 KB โ 40 B |
ctx_stats |
Show context savings, call counts, and session statistics. | โ |
ctx_doctor |
Diagnose installation: runtimes, hooks, FTS5, versions. | โ |
ctx_upgrade |
Upgrade to latest version from GitHub, rebuild, reconfigure hooks. | โ |
ctx_purge |
Permanently deletes all indexed content from the knowledge base. | โ |
How the Sandbox Works
Each ctx_execute call spawns an isolated subprocess with its own process boundary. Scripts can't access each other's memory or state. The subprocess runs your code, captures stdout, and only that stdout enters the conversation context. The raw data โ log files, API responses, snapshots โ never leaves the sandbox.
Twelve language runtimes are available: JavaScript, TypeScript, Python, Shell, Ruby, Go, Rust, PHP, Perl, R, Elixir, and C#. Bun is auto-detected for 3-5x faster JS/TS execution.
Authenticated CLIs work through credential passthrough โ gh, aws, gcloud, kubectl, docker inherit environment variables and config paths without exposing them to the conversation.
When output exceeds 5 KB and an intent is provided, Context Mode switches to intent-driven filtering: it indexes the full output into the knowledge base, searches for sections matching your intent, and returns only the relevant matches with a vocabulary of searchable terms for follow-up queries.
How the Knowledge Base Works
The ctx_index tool chunks markdown content by headings while keeping code blocks intact, then stores them in a SQLite FTS5 (Full-Text Search 5) virtual table. The SQLite backend is selected automatically at runtime: bun:sqlite on Bun, node:sqlite on Node.js >= 22.5, and better-sqlite3 everywhere else. Search uses BM25 ranking โ a probabilistic relevance algorithm that scores documents based on term frequency, inverse document frequency, and document length normalization. Porter stemming is applied at index time so "running", "runs", and "ran" match the same stem. Titles and headings are weighted 5x in BM25 scoring for precise navigational queries.
When you call ctx_search, it returns relevant content snippets focused around matching query terms โ not full documents, not approximations, the actual indexed content with smart extraction around what you're looking for. ctx_fetch_and_index extends this to URLs: fetch, convert HTML to markdown, chunk, index. The raw page never enters context. Use the contentType parameter to filter results by type (e.g. code or prose).
Ranking: Reciprocal Rank Fusion
Search runs two parallel strategies and merges them with Reciprocal Rank Fusion (RRF):
- Porter stemming โ FTS5 MATCH with porter tokenizer. "caching" matches "cached", "caches", "cach".
- Trigram substring โ FTS5 trigram tokenizer matches partial strings. "useEff" finds "useEffect", "authenticat" finds "authentication".
RRF merges both ranked lists into a single result set, so a document that ranks well in both strategies surfaces higher than one that ranks well in only one. This replaces the old cascading fallback approach where trigram results were only used if porter returned nothing.
Proximity Reranking
Multi-term queries get an additional reranking pass. Results where query terms appear close together are boosted โ "session continuity" ranks passages with adjacent terms higher than pages where "session" and "continuity" appear paragraphs apart.
Fuzzy Correction
Levenshtein distance corrects typos before re-searching. "kuberntes" becomes "kubernetes", "autentication" becomes "authentication".
Smart Snippets
Search results use intelligent extraction instead of truncation. Instead of returning the first N characters (which might miss the important part), Context Mode finds where your query terms appear in the content and returns windows around those matches.
TTL Cache
Indexed content persists in a per-project SQLite database at ~/.context-mode/content/. When ctx_fetch_and_index is called for a URL that was already indexed within its TTL window, the fetch is skipped entirely and the model searches the existing index directly.
- Default TTL: 24 hours. Override per-call with
ttl: <milliseconds>(PR #666). Longer for stable specs, shorter for changelogs you want re-checked often. - Cache hit (within TTL): Returns a cache hint (~0.3KB) instead of re-fetching (48KB+). Model proceeds to
ctx_search. - Cache miss (TTL expired): Re-fetches silently. No user action needed.
ttl: 0orforce: true: Bypasses cache and re-fetches regardless of freshness.- 14-day cleanup: Content databases and sources older than 14 days are removed on startup.
This means --continue sessions preserve indexed docs across restarts. No re-fetching, no wasted context tokens.
ctx_stats reports cache performance separately: hits, data avoided, network requests saved, and total context savings including cache.
Progressive Throttling
- Calls 1-3: Normal results (2 per query)
- Calls 4-8: Reduced results (1 per query) + warning
- Calls 9+: Blocked โ redirects to
ctx_batch_execute
Session Continuity
When the context window fills up, the agent compacts the conversation โ dropping older messages to make room. Without session tracking, the model forgets which files it was editing, what tasks are in progress, what errors were resolved, and what you last asked for.
Context Mode captures every meaningful event during your session and persists them in a per-project SQLite database. When the conversation compacts (or you resume with --continue, --resume, or /resume), your working state is rebuilt automatically โ the model continues from your last prompt without asking you to repeat anything.
Resuming a non-latest session via
/resumeworks the same way: the SessionStart hook detects the empty live-event table for the freshly issued session id and falls back to the most recent unconsumed snapshot for the project (session_resumetable). The picker selects the conversation; context-mode rehydrates the prior working state.
Session continuity requires 5 hooks working together:
| Hook | Role | Claude Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Antigravity | Antigravity CLI (agy) |
Kiro | Zed | Pi | OMP |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PreToolUse | Enforces sandbox routing before tool execution | Yes | -- | -- | -- | Yes | Yes | -- | -- | -- | Yes | -- | Bounded | Yes | -- | โ (via tool_call event) | โ (via tool_call event) |
| PostToolUse | Captures events after each tool call | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | -- | Yes (capture-only) | Yes | -- | โ (via tool_result event) | โ (via tool_result event) |
| UserPromptSubmit | Captures user decisions and corrections | Yes | -- | -- | -- | Yes | -- | Plugin (via chat.message) | Plugin (via chat.message) | -- | Yes | -- | -- | -- | -- | -- | -- |
| Stop | Captures assistant turn-end state | Yes | -- | -- | -- | Yes | Yes | -- | -- | -- | Yes | -- | Best-effort | -- | -- | -- | -- |
| PreCompact | Builds snapshot before compaction | Yes | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | Yes | -- | -- | -- | -- | โ (via session_before_compact) | โ (via session_before_compact) |
| SessionStart | Restores state after compaction or resume | Yes | Yes | Yes | Yes | Yes | -- | โ (via experimental.chat.system.transform) | โ (via experimental.chat.system.transform) | Plugin | Yes | -- | -- | -- | -- | โ (via session_start event) | โ (via session_start event) |
| Session completeness | Full | High | High | High | High | Partial | Full | Full | High | Partial | -- | Partial | Partial | -- | High | High |
Note: Full session continuity (capture + snapshot + restore) works on Claude Code, Gemini CLI, VS Code Copilot, JetBrains Copilot, OpenCode, and KiloCode. GitHub Copilot CLI uses its own camelCase hook config keys (
preToolUse,postToolUse,preCompact,sessionStart,userPromptSubmitted,agentStop) and top-level hook responses; it captures prompt, tool, compaction, session-start, and stop events when the plugin hooks are installed. OpenCode and KiloCode useexperimental.chat.system.transformas a SessionStart surrogate to inject the routing block and restore prior sessions, pluschat.messagefor user-prompt capture; full SessionStart hook support is not yet available (#14808, #5409), but prior-session continuity and user-decision capture work fully. Cursor captures tool events viapreToolUse/postToolUse, butsessionStartis currently rejected by Cursor's validator (forum report), so session restore after compaction is not available yet. OpenClaw uses native gateway plugin hooks (api.on()) for full session continuity. Pi Coding Agent provides high session continuity via extension hooks (tool_call,tool_result,session_start,session_before_compact). Codex CLI provides partial hook-based session tracking through PreToolUse, PostToolUse, PreCompact, SessionStart, UserPromptSubmit, and Stop; MCP tools work. Antigravity IDE and Zed have no hook support in the current release, so session tracking is not available there. Antigravity CLI (agy) is separate from the IDE and supports boundedPreToolUse, capture-onlyPostToolUse, and best-effortStopthrough its plugin hooks. Kiro captures tool events via nativepreToolUse/postToolUsehooks, but its SessionStart equivalent (agentSpawn) is not yet wired, so session restore after compaction is unavailable. OMP (Oh My Pi) ships full plugin-based hook support โomp plugin install context-moderegisterstool_call,tool_result,session_start, andsession_before_compacthandlers and storage roots cleanly under~/.omp/context-mode/so OMP and Pi installs never share state.
What gets captured
Every tool call passes through hooks that extract structured events:
| Category | Events | Priority | Captured By |
|---|---|---|---|
| Files | read, edit, write, glob, grep | Critical (P1) | PostToolUse |
| Tasks | create, update, complete | Critical (P1) | PostToolUse |
| Plans | enter, exit, approved, rejected, file write | Critical (P1) | PostToolUse |
| Rules | CLAUDE.md / GEMINI.md / AGENTS.md paths + content | Critical (P1) | SessionStart |
| User Prompts | Every user message (for last-prompt restore) | Critical (P1) | UserPromptSubmit |
| Decisions | User corrections, preferences ("use X instead", "don't do Y") | High (P2) | UserPromptSubmit |
| Git | checkout, commit, merge, rebase, stash, push, pull, diff, status | High (P2) | PostToolUse |
| Errors | Tool failures, non-zero exit codes | High (P2) | PostToolUse |
| Error Resolution | Error โ fix pairs detected across sequential tool calls | High (P2) | PostToolUse |
| Constraints | Discovered limitations ("not supported", "permission denied") | High (P2) | PostToolUse |
| Blockers | "blocked on", "waiting for", "depends on" โ tracked until resolved | High (P2) | UserPromptSubmit |
| Rejected Approaches | Tool calls denied by user (PreToolUse โ PostToolUse marker) | High (P2) | PreToolUse |
| Environment | cwd changes, venv, nvm, conda, worktree, package installs | High (P2) | PostToolUse |
| Agent Findings | Completed subagent results (first 500 chars) | High (P2) | PostToolUse |
| Iteration Loops | Same tool called 3+ times with similar input (retry detection) | High (P2) | PostToolUse |
| Latency | Tool calls exceeding 5s (tool name + duration in ms) | Normal (P3) | PreToolUse |
| MCP Tools | All mcp__* tool calls with usage counts |
Normal (P3) | PostToolUse |
| Subagents | Agent tool launches and completions | Normal (P3) | PostToolUse |
| Skills | Slash command invocations | Normal (P3) | PostToolUse |
| External Refs | URLs, GitHub issue references (#123), deduped | Normal (P3) | PostToolUse |
| Role | Persona / behavioral directives ("act as senior engineer") | Normal (P3) | UserPromptSubmit |
| Intent | Session mode classification (investigate, implement, review) | Low (P4) | UserPromptSubmit |
| Data | Large user-pasted data references (>1 KB) | Low (P4) | UserPromptSubmit |
How sessions survive compaction
PreCompact fires
โ Read all session events from SQLite
โ Build priority-tiered XML snapshot (โค2 KB)
โ Store snapshot in session_resume table
SessionStart fires (source: "compact")
โ Retrieve stored snapshot
โ Write structured events file โ auto-indexed into FTS5
โ Build Session Guide with 15 categories
โ Inject <session_knowledge> directive into context
โ Model continues from last user prompt with full working stateThe snapshot is built in priority tiers โ if the 2 KB budget is tight, lower-priority events (intent, MCP tool counts) are dropped first while critical state (active files, tasks, rules, decisions) is always preserved.
After compaction, the model receives a Session Guide โ a structured narrative with actionable sections:
- Last Request โ user's last prompt, so the model continues without asking "what were we doing?"
- Tasks โ checkbox format with completion status (
[x]completed,[ ]pending) - Plans โ plan mode entries, exits, approvals, and rejections
- Key Decisions โ user corrections and preferences ("use X instead", "don't do Y")
- Files Modified โ all files touched during the session
- Unresolved Errors โ errors that haven't been fixed, plus errorโfix resolution pairs
- Constraints โ discovered limitations and boundaries
- Blockers โ open and resolved blockers ("blocked on X", "waiting for Y")
- Git โ operations performed (checkout, commit, push, status)
- Project Rules โ CLAUDE.md / GEMINI.md / AGENTS.md paths
- MCP Tools Used โ tool names with call counts
- Subagent Tasks โ delegated work summaries + agent findings
- Skills Used โ slash commands invoked
- Rejected Approaches โ tool calls the user denied
- External References โ URLs and GitHub issue references
- Environment โ working directory, env variables, worktrees
- Data References โ large data pasted during the session
- Session Intent โ mode classification (implement, investigate, review, discuss)
- User Role โ behavioral directives set during the session
Detailed event data is also indexed into FTS5 for on-demand retrieval via ctx_search().
Per-platform details
Claude Code โ Full session support. All 5 hook types fire, capturing tool events, user decisions, building compaction snapshots, and restoring state after compaction, --continue, --resume, or /resume.
Gemini CLI โ High coverage. PostToolUse (AfterTool), PreCompact (PreCompress), and SessionStart all fire. Missing UserPromptSubmit, so user decisions and corrections aren't captured โ but file edits, git ops, errors, and tasks are fully tracked.
VS Code Copilot โ High coverage. Same as Gemini CLI โ PostToolUse, PreCompact, and SessionStart all fire. User decisions aren't captured but all tool-level events are.
JetBrains Copilot โ High coverage. Same capabilities as VS Code Copilot โ PostToolUse, PreCompact, and SessionStart all fire. Uses the same hook wire protocol and response format. User decisions aren't captured but all tool-level events are.
GitHub Copilot CLI โ High coverage. Native plugin hooks use camelCase config keys (preToolUse, postToolUse, preCompact, sessionStart, userPromptSubmitted, agentStop) and top-level hook response fields. The plugin captures user prompts, tool events, compaction snapshots, session start restore, and stop events.
Cursor โ Partial coverage. Native preToolUse and postToolUse hooks capture tool events. sessionStart is documented by Cursor but currently rejected by their validator, so session restore is not available. Routing instructions are delivered via MCP server startup instead.
OpenCode โ Full session support. The TypeScript plugin captures PostToolUse events via tool.execute.after, user prompts and decisions via chat.message, builds compaction snapshots via experimental.session.compacting, and restores prior sessions via experimental.chat.system.transform (SessionStart surrogate). Routing block is injected on first chat.system.transform per session. AGENTS.md/CLAUDE.md/CONTEXT.md rules are captured automatically on first hook fire.
KiloCode โ Full session support. Shares the same plugin architecture as OpenCode via the OpenCodeAdapter. The TypeScript plugin captures PostToolUse events via tool.execute.after, user prompts and decisions via chat.message, builds compaction snapshots via experimental.session.compacting, and restores prior sessions via experimental.chat.system.transform (SessionStart surrogate).
OpenClaw / Pi Agent โ High coverage. All tool lifecycle hooks (after_tool_call, before_compaction, session_start) fire via the native gateway plugin. User decisions aren't captured but file edits, git ops, errors, and tasks are fully tracked. Falls back to DB snapshot reconstruction if compaction hooks fail on older gateway versions. See docs/adapters/openclaw.md.
Codex CLI โ MCP active, hooks require [features].hooks = true. Hook scripts (PreToolUse, PostToolUse, PreCompact, SessionStart, UserPromptSubmit, Stop) are implemented and tested; PreCompact remains runtime-gated on Codex builds that emit the event. PreToolUse deny routing works; input rewriting still depends on upstream updatedInput support (openai/codex#18491).
Antigravity โ No session support. No hooks, no event capture. Requires manually copying GEMINI.md to your project root. Auto-detected via MCP protocol handshake (clientInfo.name).
Antigravity CLI (agy) โ Partial coverage. The standalone CLI is separate from the IDE and supports bounded native PreToolUse enforcement for mapped high-flood tools, capture-only PostToolUse, and best-effort Stop through the shipped plugin hooks. It does not currently provide PreCompact/SessionStart/UserPromptSubmit coverage.
Zed โ No session support. No hooks, no event capture. Requires manually copying AGENTS.md to your project root. Auto-detected via MCP protocol handshake (clientInfo.name).
Kiro โ Partial coverage. Native preToolUse and postToolUse hooks capture tool events and enforce sandbox routing. agentSpawn (the Kiro equivalent of SessionStart) is not yet implemented, so session restore after compaction is not available. Requires manually copying KIRO.md to your project root. Auto-detected via MCP protocol handshake (clientInfo.name).
Pi Coding Agent โ High coverage. The extension registers all key lifecycle events: tool_call (PreToolUse), tool_result (PostToolUse), session_start (SessionStart), and session_before_compact (PreCompact). File edits, git ops, errors, and tasks are fully tracked. Session restore after compaction works via the extension's event hooks.
Tool call output can be collapsed/expanded with the default Pi's default keybinding (Ctrl+O)
OMP (Oh My Pi) โ High coverage. The plugin (installed via omp plugin install context-mode) registers all key lifecycle events: tool_call (PreToolUse), tool_result (PostToolUse), session_start (SessionStart), and session_before_compact (PreCompact). Storage roots cleanly under ~/.omp/context-mode/ so OMP and Pi installs never share state (issue #473). Auto-detected via PI_CODING_AGENT_DIR env var or presence of ~/.omp/.
Platform Compatibility
| Feature | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Kimi Code | Antigravity | Antigravity CLI (agy) |
Kiro | Zed | Pi | OMP |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| MCP Server / Native Tools | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Native plugin | Native plugin | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| PreToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | Yes | -- | Bounded | Yes | -- | Yes (extension) | Plugin |
| PostToolUse Hook | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | Yes | -- | Yes (capture-only) | Yes | -- | Yes (extension) | Plugin |
| SessionStart Hook | Yes | Yes | Yes | Yes | Yes | Yes | -- | โ (via experimental.chat.system.transform) | โ (via experimental.chat.system.transform) | Plugin | Yes | Yes | -- | -- | -- | -- | Yes (extension) | Plugin |
| PreCompact Hook | Yes | Yes | Yes | Yes | Yes | Yes | -- | Plugin | Plugin | Plugin | Yes | Yes | -- | -- | -- | -- | Yes (extension) | Plugin |
| Can Modify Args | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | -- | Yes | -- | -- | -- | -- | Yes (extension) | -- |
| Can Block Tools | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Plugin | Plugin | Plugin | Yes | Yes | -- | Bounded | Yes | -- | Yes (extension) | Plugin |
| Utility Commands (ctx) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes (/ctx-stats, /ctx-doctor) | Yes |
| Slash Commands | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
| Plugin Marketplace | Yes | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
OpenCode uses a TypeScript plugin paradigm โ hooks run as in-process functions via
tool.execute.before,tool.execute.after,experimental.session.compacting,experimental.chat.system.transform, andchat.message, providing full routing enforcement, session continuity, and user-prompt capture. Theexperimental.chat.system.transformhook acts as a SessionStart surrogate to inject the routing block and restore prior sessions. Thechat.messagehook captures user prompts and decisions (UserPromptSubmit equivalent).KiloCode shares the same TypeScript plugin architecture as OpenCode via the OpenCodeAdapter, with platform-specific configuration paths (
kilo.jsoninstead ofopencode.json,~/.config/kilo/instead of~/.config/opencode/). Hook capabilities match OpenCode, including SessionStart surrogate viaexperimental.chat.system.transformand user-prompt capture viachat.message.OpenClaw runs context-mode as a native gateway plugin targeting Pi Agent sessions. Hooks register via
api.on()(tool/lifecycle) andapi.registerHook()(commands). All tool interception and compaction hooks are supported. Seedocs/adapters/openclaw.md.Codex CLI hooks require
[features].hooks = true. MCP tools work, and hook scripts activate through$CODEX_HOME/hooks.jsonor~/.codex/hooks.json. PreToolUse supportspermissionDecision: "deny"only; input modification still needs upstreamupdatedInputsupport (openai/codex#18491).additionalContextis not supported in PreToolUse (context injection works via PostToolUse and SessionStart instead; the codex formatter handles this automatically). PreCompact stores resume snapshots before compaction on Codex builds that emit the event, SessionStart restores them, and UserPromptSubmit/Stop capture prompt and turn-end continuity events. See the Codex install section for setup. Antigravity and Zed do not support hooks. They rely solely on manually-copied routing instruction files (AGENTS.md/GEMINI.md) for enforcement (~60% compliance). See each platform's install section for copy instructions. Antigravity and Zed are auto-detected via MCP protocol handshake โ no manual platform configuration needed.Antigravity CLI (
agy) supports boundedPreToolUseblocking for mapped Bash/Read/Grep/WebFetch surfaces, plusPostToolUsecapture and best-effortStopcapture through its pluginhooks.json. The routing rule and routing skill remain the broader instruction layer;PreInvocation/PostInvocationare not wired until their payload/response semantics are verified.Kiro supports native
preToolUseandpostToolUsehooks for routing enforcement and tool event capture.agentSpawn(SessionStart equivalent) andstopare not yet wired. Requires manually copyingKIRO.mdto your project root. Kiro is auto-detected via MCP protocol handshake (clientInfo.name).Pi Coding Agent runs context-mode as an extension with full hook support. The extension registers
tool_call,tool_result,session_start, andsession_before_compactevents, providing high session continuity coverage. The MCP server provides all 11 MCP tools.OMP (Oh My Pi) runs context-mode as a plugin via
omp plugin install context-mode. The plugin registerstool_call,tool_result,session_start, andsession_before_compactevents for hard-block routing and full session continuity. Storage isolated under~/.omp/context-mode/so OMP and Pi never share state. Auto-detected viaPI_CODING_AGENT_DIR(default agent dir~/.omp/agent) or~/.omp/directory. See issue #473 for the storage-isolation history.
Routing Enforcement
Hooks intercept tool calls programmatically โ they can block dangerous commands and redirect them to the sandbox before execution. Instruction files guide the model via prompt instructions but cannot block anything. Always enable hooks where supported.
Note: Routing instruction files were previously auto-written to project directories on first session start. This was disabled to prevent git tree pollution (#158, #164). Hook-capable platforms (Claude Code, Gemini CLI, VS Code Copilot, JetBrains Copilot, GitHub Copilot CLI, Cursor, OpenCode, OpenClaw, Codex CLI, Antigravity CLI for bounded tool hooks, Kiro for tool hooks, OMP via plugin) inject or enforce routing without writing files. Platforms without hook support โ Zed and Antigravity IDE โ require a one-time manual copy of the routing file; see each platform's install section.
| Platform | Hooks | Instruction File | With Hooks | Without Hooks |
|---|---|---|---|---|
| Claude Code | Yes (auto) | CLAUDE.md |
~98% saved | ~60% saved |
| Gemini CLI | Yes | GEMINI.md |
~98% saved | ~60% saved |
| VS Code Copilot | Yes | copilot-instructions.md |
~98% saved | ~60% saved |
| JetBrains Copilot | Yes | copilot-instructions.md |
~98% saved | ~60% saved |
| GitHub Copilot CLI | Yes | copilot-instructions.md |
~98% saved | ~60% saved |
| Cursor | Yes | context-mode.mdc |
~98% saved | ~60% saved |
| OpenCode | Plugin | AGENTS.md |
~98% saved | ~60% saved |
| OpenClaw | Plugin | AGENTS.md |
~98% saved | ~60% saved |
| Codex CLI | Yes | AGENTS.md |
~98% saved | ~60% saved |
| Antigravity | -- | GEMINI.md |
-- | ~60% saved |
Antigravity CLI (agy) |
Bounded | routing rule + skill (rules, skill) |
bounded Bash/Read/Grep/WebFetch enforcement | ~60% saved |
| Kiro | Yes | KIRO.md |
~98% saved | ~60% saved |
| Zed | -- | AGENTS.md |
-- | ~60% saved |
| Pi | โ | AGENTS.md |
~98% saved | ~60% saved |
| OMP | Plugin | SYSTEM.md |
~98% saved | ~60% saved |
Without hooks, one unrouted curl or Playwright snapshot can dump 56 KB into context โ wiping out an entire session's worth of savings.
See docs/platform-support.md for the full capability comparison.
Utility Commands
Inside any AI session โ just type the command. The LLM calls the MCP tool automatically:
ctx stats โ context savings, call counts, session report
ctx doctor โ diagnose runtimes, hooks, FTS5, versions
ctx index โ index a local file or directory for later search
ctx search โ search previously indexed content
ctx upgrade โ update from GitHub, rebuild, reconfigure hooks
ctx purge โ permanently delete all indexed content from the knowledge base
ctx insight โ opens the hosted Insight dashboard in your browserFrom your terminal โ run directly without an AI session:
context-mode doctor
context-mode index . --source project:my-app
context-mode search "authentication middleware" --source project:my-app
context-mode upgrade
context-mode insight # opens the hosted Insight dashboard in browser
bash scripts/ctx-debug.sh # full diagnostic report for bug reportsThe debug script collects OS info, runtime versions, better-sqlite3 status, adapter detection, config files (redacted), hook validation, FTS5/SQLite test, executor test, process check, session databases, and environment variables into a single pasteable markdown report.
Works on all platforms. On Claude Code, slash commands (/ctx-stats, /ctx-doctor, /ctx-index, /ctx-search, /ctx-upgrade, /ctx-purge, /ctx-insight) are also available.
Benchmarks
| Scenario | Raw | Context | Saved |
|---|---|---|---|
| Playwright snapshot | 56.2 KB | 299 B | 99% |
| GitHub Issues (20) | 58.9 KB | 1.1 KB | 98% |
| Access log (500 requests) | 45.1 KB | 155 B | 100% |
| Context7 React docs | 5.9 KB | 261 B | 96% |
| Analytics CSV (500 rows) | 85.5 KB | 222 B | 100% |
| Git log (153 commits) | 11.6 KB | 107 B | 99% |
| Test output (30 suites) | 6.0 KB | 337 B | 95% |
| Repo research (subagent) | 986 KB | 62 KB | 94% |
Over a full session: 315 KB of raw output becomes 5.4 KB. Session time extends from ~30 minutes to ~3 hours.
Full benchmark data with 21 scenarios โ
Try It
These prompts work out of the box. Run /context-mode:ctx-stats after each to see the savings.
Deep repo research โ 5 calls, 62 KB context (raw: 986 KB, 94% saved)
Research https://github.com/modelcontextprotocol/servers โ architecture, tech stack,
top contributors, open issues, and recent activity. Then run /context-mode:ctx-stats.Git history analysis โ 1 call, 5.6 KB context
Clone https://github.com/facebook/react and analyze the last 500 commits:
top contributors, commit frequency by month, and most changed files.
Then run /context-mode:ctx-stats.Web scraping โ 1 call, 3.2 KB context
Fetch the Hacker News front page, extract all posts with titles, scores,
and domains. Group by domain. Then run /context-mode:ctx-stats.Large JSON API โ 7.5 MB raw โ 0.9 KB context (99% saved)
Create a local server that returns a 7.5 MB JSON with 20,000 records and a secret
hidden at index 13000. Fetch the endpoint, find the hidden record, and show me
exactly what's in it. Then run /context-mode:ctx-stats.Documentation search โ 2 calls, 1.8 KB context
Fetch the React useEffect docs, index them, and find the cleanup pattern
with code examples. Then run /context-mode:ctx-stats.Session continuity โ compaction recovery with full state
Start a multi-step task: "Create a REST API with Express โ add routes, tests,
and error handling." After 20+ tool calls, type: ctx stats to see the session
event count. When context compacts, the model continues from your last prompt
with tasks, files, and decisions intact โ no re-prompting needed.Privacy & Architecture
Context Mode is not a CLI output filter or a cloud analytics dashboard. It operates at the MCP protocol layer โ raw data stays in a sandboxed subprocess and never enters your context window. Web pages, API responses, file analysis, Playwright snapshots, log files โ everything is processed in complete isolation.
Nothing leaves your machine. No telemetry, no cloud sync, no usage tracking, no account required. Your code, your prompts, your session data โ all local. The SQLite databases live in your home directory and die when you're done.
This is a deliberate architectural choice, not a missing feature. Context optimization should happen at the source, not in a dashboard behind a per-seat subscription. Privacy-first is our philosophy โ and every design decision follows from it. License โ
Security
Context Mode enforces the same permission rules you already use โ but extends them to the MCP sandbox. If you block sudo, it's also blocked inside ctx_execute, ctx_execute_file, and ctx_batch_execute.
Zero setup required. If you haven't configured any permissions, nothing changes. This only activates when you add rules.
{
"permissions": {
"deny": [
"Bash(sudo *)",
"Bash(rm -rf /*)",
"Read(.env)",
"Read(**/.env*)"
],
"allow": [
"Bash(git:*)",
"Bash(npm:*)"
]
}
}Add this to your project's .claude/settings.json (or ~/.claude/settings.json for global rules). All platforms read security policies from Claude Code's settings format โ even on Gemini CLI, VS Code Copilot, and OpenCode. Codex CLI security enforcement requires the Codex hooks in $CODEX_HOME/hooks.json or ~/.codex/hooks.json to be configured.
The pattern is Tool(what to match) where * means "anything".
Commands chained with &&, ;, or | are split โ each part is checked separately. echo hello && sudo rm -rf /tmp is blocked because the sudo part matches the deny rule.
deny always wins over allow. More specific (project-level) rules override global ones.
Project-boundary containment
ctx_execute_file is confined to the project root. A path that resolves outside the workspace โ an absolute path like /home/user/secrets, a ../../ traversal, or a project-local symlink whose target escapes the project โ is refused with a File access blocked error. This closes the #852 escape vector where an agent, denied an out-of-project read by the host sandbox, retried through the MCP sandbox (the host's MCP approval prompt cannot inspect the tool's input params, so the escape was invisible to the approver).
The guard is on by default and requires no configuration. To intentionally process a file outside the project (e.g. a shared log under /var/log), opt that path back in with the same permissions.allow rule you already use for the host Read tool โ there is no context-mode-specific env flag:
{
"permissions": {
"allow": ["Read(/var/log/**)"]
}
}context-mode honors that allow rule (read from your .claude/settings.json / ~/.claude/settings.json) exactly as Claude Code does, so an out-of-project grant lives in one place and stays meaningful.
Reviewing the prompt: the ctx_execute / ctx_execute_file approval titles now read as code execution ("Run code in a sandboxโฆ", "Run code over a fileโฆ") so an unfamiliar reviewer can recognise the action class even though the MCP prompt renders only the tool title and raw arguments. ctx_execute and ctx_batch_execute run arbitrary code and still inherit the process's filesystem access, so the boundary guard is a defense-in-depth layer for the file-read tool, not a full OS sandbox โ treat approving any execution tool as approving arbitrary code, and keep host-level sandboxing enabled.
Network fetch hardening
ctx_fetch_and_index blocks dangerous URL targets by default:
- Schemes: only
http:andhttps:allowed (nofile://,gopher://,javascript:,data:). - Cloud metadata + link-local:
169.254.0.0/16(incl. AWS/GCP/Azure IMDS endpoint169.254.169.254) hard-blocked even if a hostname resolves to it (DNS-rebinding defense). - Multicast / reserved:
224.0.0.0/4,0.0.0.0/8, IPv6ff00::/8,fe80::/10blocked. - Loopback + RFC1918 (
localhost,127.x,10.x,172.16-31.x,192.168.x, IPv6::1,fc00::/7) allowed by default so local dev servers + internal-network fetches keep working.
For hosted/CI environments where you want to block private targets too, set:
export CTX_FETCH_STRICT=1That blocks loopback + RFC1918 + ULA in addition to the always-blocked ranges. Useful when context-mode runs as a shared service, not on a developer's own machine.
tool_input for any mcp__* tool call is also redacted before persistence โ the regex matcher in hooks/posttooluse.mjs masks authorization, auth_token, access_token, refresh_token, bearer, token, secret, password, passwd, pwd, api_key / apikey / x_api_key, cookie / set-cookie, signature, private_key, and client_secret (case-insensitive, hyphen/underscore-insensitive) to [REDACTED] so credentials in MCP arguments don't end up in the session DB.
Storage environment variables
| Variable | Default | Purpose |
|---|---|---|
CONTEXT_MODE_DIR |
Adapter default, for example ~/.codex/context-mode or ~/.claude/context-mode |
Since v1.0.147. Absolute writable root for context-mode storage. Sessions and stats use <root>/sessions; indexed content uses <root>/content. Empty or whitespace-only values are treated as unset and shown by ctx_doctor; non-empty values must be absolute. ~ is not expanded. |
Routing-guidance environment variables
| Variable | Default | Purpose |
|---|---|---|
CONTEXT_MODE_EXTERNAL_MCP_NUDGE_EVERY |
10 |
Cadence (in tool calls) at which the PreToolUse hook re-injects the "wrap large external-MCP payloads in ctx_execute" guidance. The original implementation (#529) fired only once per session, which got lost after context compaction in MCP-heavy sessions (e.g. 50+ Jira/Slack/Notion calls โ see #567 follow-up). The default re-fires every 10th matching call, keeping the guidance in the model's recent window. Range [1, 100]; invalid values fall back to 10. Set to 1 for "every call" (most aggressive โ adds ~250 tokens/call) or to a larger value for less frequent reminders. |
Contributing
See CONTRIBUTING.md for the development workflow and TDD guidelines.
git clone https://github.com/mksglu/context-mode.git
cd context-mode && npm install && npm testLicense
Licensed under Elastic License 2.0 (source-available). You can use it, fork it, modify it, and distribute it. Two things you can't do: offer it as a hosted/managed service, or remove the licensing notices. We chose ELv2 over MIT because MIT permits repackaging the code as a competing closed-source SaaS โ ELv2 prevents that while keeping the source available to everyone.
File: docs/adr/0001-sessiondb-multi-writer.md
ADR 0001 โ SessionDB is multi-writer-safe
- Status: Accepted
- Date: 2026-05-14
- Version: v1.0.130
- Supersedes: the v1.0.128 single-writer guard and its v1.0.129 hotfix
- Reviewers: 10 parallel grill verdicts (UX, SQLite, Security, Perf, Architect, Test, SRE, PM, Data, DevEx)
Context
v1.0.128 introduced two new defenses inside SQLiteBase (the shared
ctor for SessionDB and ContentStore):
acquireDbLock(dbPath)โ an O_EXCL<dbPath>.lockfile containing
the owning PID. Subsequent openers that found a live PID inside the
lockfile threwDatabaseLockedError("Another context-mode server is already running (PID: N). Stop it before starting a new instance.").db.pragma("locking_mode = EXCLUSIVE")โ applied immediately afterapplyWALPragmas, on both the main open path and the corruption
recovery path.
The stated goal was to fix issue #560: @ishabana reported a 5-process
scenario where multiple context-mode MCP servers writing the same on-
disk SQLite content store unbounded the WAL โ readers held shared locks
indefinitely so wal_checkpoint(TRUNCATE) never fired, the only
existing truncation path is closeDB's checkpoint on graceful exit
(which #559's zombie servers never reached), and the result was
238MB+ WAL files plus ctx_search hangs.
v1.0.129 added a tmpdir skip-gate to both primitives because the test
suite (which opens many DBs on tmp paths in the same process) tripped
the lockfile + SQLITE_BUSY from the EXCLUSIVE pragma.
After v1.0.128 + v1.0.129 shipped, multi-window users โ who legitimately
run two Claude sessions against the same project DB โ hit theDatabaseLockedError and could not work. That is the regression this
ADR rolls out.
Root cause re-analysis
#560's actual root causes were not "two MCP processes opened the same
DB at the same time." They were:
- #559 (zombie MCP child accumulation). After
/ctx-upgrade, the
previous MCP server child was not killed, so old + new processes
both ran, both wrote, and neither ever exited gracefully (so the WAL
truncation incloseDBnever fired). Fixed in v1.0.128 by killing
the previous child before starting a new one. - #561 (Pi misdetection writing to
~/.claude/context-mode/).
Adapter detection accidentally fired on Pi installs, anddefaultDBPath()produced a path that was shared across user
sessions instead of the per-process tmp DB shape. Fixed in v1.0.129
by scrubbing foreign identification env on bridge spawn.
With both root causes fixed, normal usage is one MCP process per
Claude session per project. Legitimate multi-window UX is two
processes on the same on-disk dbPath โ and the SQLite WAL handles
that natively. The lockfile was solving a problem that no longer
existed once #559 + #561 were fixed.
Decision
SessionDBis multi-writer-safe. So isContentStore. Both are
built onSQLiteBase, soSQLiteBasector MUST NOT apply any
single-writer enforcement.acquireDbLock,releaseDbLock,DatabaseLockedError, and the
wholesrc/util/db-lock.tsmodule are deleted (v1.0.130 slice 4).db.pragma("locking_mode = EXCLUSIVE")is removed from the ctor.applyWALPragmasalready does not apply it.withRetry()(busy_timeout = 30000ms insidenew Database(...),
bounded retry loop onSQLITE_BUSY) remains the documented
contract for handling write contention. That is the SQLite-native
multi-writer story.
Regression-proof anchor
Two paired tests in tests/util/db-base-platform-gate.test.ts:
- Behavioural (
v1.0.130 INVARIANT โ SQLiteBase multi-writer default > "INVARIANT: two SQLiteBase instances on the same tmpdir path can both open and write (multi-writer default)"). Opens twoSessionDBinstances on the same on-disk path (NOT tmpdir), writes
through both viainsertEvent, asserts neither throws. - Source-pin (
"INVARIANT: SQLiteBase ctor must NOT contain acquireDbLock or locking_mode=EXCLUSIVE"). Reads the source ofsrc/db-base.ts, scopes to theSQLiteBaseclass body, regex
asserts the literal identifier names +locking_mode=EXCLUSIVE
pattern do not appear.
If a future contributor pulls the v1.0.128 single-writer primitives
back into the ctor, the source-pin test fails LOUDLY in CI before
merge. If they invent a new shape that passes the source-pin but
breaks behaviourally, the behavioural test fails. Defense in depth.
Consequences
Positive
- Legitimate multi-window users can run two Claude sessions on the
same project without theDatabaseLockedErrorregression. - The 5-worktree workflow (one Claude session per worktree, separate
dbPath per worktree) keeps working โ that path was never affected
by the lockfile, but it is documented here as covered by the
multi-writer contract. - ContentStore (FTS5 shared knowledge base across sessions) โ which
was always multi-writer by design โ no longer needs theapplyWALPragmas
guard againstapplyWALPragmasaccidentally applying EXCLUSIVE. - Deletes 185 lines of code (
db-lock.ts) plus 80+ lines of
ctor-side plumbing. Smaller, simpler, faster to reason about.
Negative
- A future bug shaped like "two processes both opened the same DB
and it broke" will not be diagnosed by the lockfile error message.
It will surface asSQLITE_BUSYafter the 30s busy_timeout +
boundedwithRetryloop, which is a less specific message. We
accept this โ the fix lives in the process layer, not the DB layer. - If a future regression in the process layer (sibling-mcp) lets two
MCP children both run against the same project, the WAL will grow
unbounded again. The mitigation is the existing_liveDBsexit-hook
WAL checkpoint (which the rollback preserved) plus the periodic
optimize loop inContentStore.
Neutral
- Test count: net -10 tests in
db-base-platform-gate.test.ts. The 11
v1.0.128 lockfile tests are gone; they are replaced by 3 v1.0.130
tests (2 INVARIANT + 1 lifecycle suite with 2 cases).
Alternatives considered
Lockfile (the v1.0.128 approach) โ rejected
The lockfile correctly enforced single-writer but solved the wrong
problem. Once #559 + #561 were fixed, the only callers it blocked were
the legitimate multi-window UX users it was never meant to target. The
"who else is on this DB" UX story was useful diagnostic value, but the
cost (broken multi-window) outweighed the benefit.
Leader election โ rejected
A "first opener wins, others become read-only followers" pattern was
floated. This is over-engineering for the actual constraint set: SQLite
WAL already provides the right semantics for two writers, and the
process layer (sibling-mcp) is the right place to enforce process
identity, not the DB layer.
`locking_mode = EXCLUSIVE` pragma โ rejected
EXCLUSIVE blocks the second opener with SQLITE_BUSY instead of a
clean error. It also requires every consumer of SQLiteBase to opt out
explicitly, and ContentStore is multi-writer by design โ so EXCLUSIVE
in a shared base class is a foot-gun. Belongs (if at all) inside an
explicit single-writer subclass, not the base.
Deferred WAL truncation hook โ partially adopted
The _liveDBs exit hook WAL checkpoint was preserved from v1.0.128.
That is the actual mitigation against unbounded WAL growth, separate
from the lockfile question. We keep it.
Reference
- v1.0.128 release notes: introduced
acquireDbLock+ EXCLUSIVE pragma
for #560. - v1.0.129 release notes: added tmpdir skip-gate hotfix because the
v1.0.128 work broke 82 tests and ContentStore concurrency. - v1.0.130 (this ADR): rolls out the single-writer guard entirely.
10 parallel grill verdicts informed this decision (untracked artifacts
in the repo root: PR-559-560-VERDICT.md, PERF-560-GRILL-VERDICT.md,SECURITY-GRILL-VERDICT.md, SQLITE-EXPERT-GRILL-VERDICT.md,SRE-VERDICT-v130-rollback.md, etc.) โ all converged on "the lockfile
is solving the wrong problem; the fix is in the process layer."
Contract for `SQLiteBase` consumers
If you build on SQLiteBase:
- Your DB MAY be opened from multiple processes on the same on-disk
path. Both ContentStore and SessionDB explicitly support this. - Wrap writes that may contend in
withRetry(). The base class exposes
it as aprotected withRetry<T>()method. - Do NOT add
db.pragma("locking_mode = EXCLUSIVE")insideSQLiteBase
orapplyWALPragmas. If you genuinely need single-writer semantics
for a new subclass, add it in that subclass's ctor and document why. - Process-identity invariants (only-one-MCP-per-project) belong in
src/util/sibling-mcp.ts, not in the DB layer.
File: docs/adr/0002-tool-description-style.md
ADR-0002 โ Tool description voice and structure
- Status: Accepted
- Date: 2026-05-24
- PR: #683 (substitutes #654)
- Supersedes: organic, undocumented description drift
- Reviewers: owner Mert, empirical A/B (38 trials ร 6 probes on Haiku + Sonnet)
Context
context-mode registers 11 ctx_* MCP tools via server.registerTool() insrc/server.ts. Each tool description is read by every host LLM
(Claude / GPT / Gemini / Llama / โฆ) at tool-selection time. Over many releases
the corpus drifted toward forbidding language โ MANDATORY:, NEVER,Do NOT, REFUSAL RULES, DESTRUCTIVE, NON-NEGOTIABLE, PREFER โ because
descriptions were patched defensively after each misroute. There was no
documented style guide and no contract test, so each new tool inherited
whichever voice the previous author preferred.
PR #654 (kerneltoast) surfaced the cost of that drift: the single
hortatory word "blocked" in a routing deny reason was misread by
Opus 4.6 as a safety/network restriction, causing the agent to capitulate
to training data instead of using the redirected tool.
A full audit (see TOOL-DESCRIPTIONS-AUDIT.md) ran 38 A/B trials across
6 empirical probes on Haiku and Sonnet against the current descriptions
and proposed rewrites. Findings:
- Heavy forbidding framing degrades tool selection on some tools (mild
but reproducible). - Heavy framing improves parameter fidelity on small models for
complex-contract tools (ctx_purgeProbe 4: 5/5 vs 3/5). The
intuition that "softer == safer" is wrong for at least one tool, so
rewrites cannot be one-size-fits-all and must be probe-gated. - PR #654's
"blocked"โ"redirected"wording fix is genuinely
corrective on Opus 4.6 (6/6 โ 0/6 capitulation on the stress probe),
invisible on Sonnet (6/6 capitulate either way), and mildly regressive
on Haiku without a paired imperative. - โ
/ โ emoji bullets inside descriptions tokenize inconsistently across
Llama / Gemini families and act as negative-example leakage (rubric #4).
Decision
All ctx_* tool descriptions registered via server.registerTool()
MUST follow this structure:
<1-line headline, <= 120 chars, imperative-positive>
WHEN:
-
WHEN NOT:
-
RETURNS:
<what the agent sees back, 1-3 lines>
EXAMPLE: <one canonical call with realistic params>The legacy alias WHEN TO USE: is accepted as a transitional form (seectx_index) but new tools MUST use WHEN:.
Canonical structure (locked rubric โ PR #683 WS3)
Future contributors do not get to re-invent section names. The
contract test in tests/core/server.test.ts enforces every rule below
on every commit; this section is the source of truth.
Section order MUST be
WHEN -> WHEN NOT -> RETURNS -> EXAMPLE.
Positive selection cues precede negative disambiguation (audit
rubric #2). A tool MAY omitWHEN NOT:when it has no sibling-tool
ambiguity, but every other canonical section is mandatory.Bullets MUST use markdown
-only. Numeric (1.,1-),
asterisk (*), and unicode (โข) bullets are rejected. Numbering
inside a routing-target description is also rejected because each
bullet should be independently true, not sequenced. Numbered
hierarchies live inhooks/routing-block.mjs(priority order in a
system-prompt injection is a different prompt surface, governed by
ADR-0003 sibling concerns).Section headers MUST be UPPERCASE + colon at the start of a
line. The token uniformity matters because GPT, Gemini, Llama,
and Claude tokenize lowercase / mixed-case section names
differently โ uppercase headers are the only shape that hits a
single token across families.Two-space bullet indent under each header. Example shape:
textWHEN: - First positive cue - Second positive cueThe contract test does not assert the literal indent count (LLMs
are tolerant of 2 vs 4) but every shipped description insrc/server.tsuses two-space indent for visual uniformity.One blank line between sections.
One canonical
EXAMPLE:per tool. Tools with two valid input
shapes (e.g.ctx_purgeper-session vs per-project) MAY include
two EXAMPLE lines back-to-back. Keep them adjacent so the
description does not interleave examples with other sections.Carve-outs (per-tool, allow-listed in the contract test):
ctx_purge:DESTRUCTIVE,SCOPES,CONTRACT. Justified by
Probe 4 empirical evidence โ heavy framing on this tool
preserves parameter fidelity on small models.DESTRUCTIVEhere
is accurate user-facing signaling, distinct from the
cross-LLM-bias negative framing the rubric forbids.
Cross-LLM rationale
The audit ran 38 A/B trials ร 6 probes across Haiku and Sonnet. The
canonical structure above is the lowest common denominator that:
- Hits a single token across Claude, GPT, Gemini, Llama families
for every section header (uppercase + colon). - Avoids tokens flagged by Constitutional AI-style RLHF priors
(FORBIDDEN,BLOCKED,NEVER,MANDATORY). - Eliminates negative-example leakage (emoji bullets โ rubric #4,
Probe 3). - Leaves room for accurate signaling where empirically required
(Probe 4ctx_purgecarve-out).
The structure is locked to make future PRs ungameable: a contributor
proposing a new tool either adheres to it (contract test passes) or
opens a new ADR amending this one (contract test fails until the
allow-list is updated).
Forbidden tokens
Descriptions MUST NOT contain:
| Token | Rationale |
|---|---|
MANDATORY: (as opener) |
Developer-policy phrasing, not a selection cue. |
BLOCKED |
Reserved for ADR-0003 CASE B (real policy restriction). |
PREFER X OVER Y |
Frames the choice as a tradeoff; use positive WHEN: instead. |
Do NOT use/read/pull |
Affirmative beats negative (rubric #2). |
Never use |
Same โ express as WHEN NOT:. |
SESSION STATE clause |
Skill/role persistence is a routing-block.mjs concern. |
โ
/ โ emoji bullets |
Tokenizer inconsistency across LLM families + negative-example leakage. |
Allowed imperative hierarchy (RFC 2119)
The MUST / SHOULD / MAY hierarchy is preserved ONLY for post-call
obligations on the agent โ never for tool-selection cues.
- MUST: post-call obligation. Example (allowed):
ctx_upgradesays
"you MUST run the returned shell command and display the output as a
checklist." This is a post-call contract, not a selection nudge. - SHOULD: strong preference with allowed exceptions.
- MAY: optional capability.
Selection cues use the WHEN: / WHEN NOT: structure instead.
Length
Descriptions SHOULD be โค 1,000 characters. Hard cap 1,500.
Exemptions
ctx_stats,ctx_doctor,ctx_insightโ minimal one-line descriptions
by design (diagnostic / GUI affordances, not routing targets).ctx_upgradeโMUSTis permitted per the post-call obligation rule
above.ctx_purgeโ rewritten in PR #683 WS2 with carve-outs (DESTRUCTIVE,SCOPES,CONTRACT) that preserve the parameter-fidelity discipline
Probe 4 measured (5/5 vs 3/5 on Haiku). The rewrite still meets the
canonicalWHEN / WHEN NOT / RETURNS / EXAMPLEstructure; the carve-out
headers coexist with it. The empirical-validation gate is documented intests/core/server.test.tsALLOWED_EXTRA_SECTIONS.
Consequences
- PR #683 rewrites the six tools where the audit showed clear voice
drift:ctx_execute,ctx_execute_file,ctx_batch_execute,ctx_search,ctx_index,ctx_fetch_and_index. - A new contract test in
tests/core/server.test.ts(tool description style contract (#683 ADR-0002)) parses everyserver.registerTool()
block and enforces the forbidden-token list + WHEN: requirement on
every commit. Cheap (no LLM call), runs on every commit, catches
drift before merge. - Voice-of-trainer text (
THINK IN CODE,MANDATORY routing rules)
lives inhooks/routing-block.mjsandCLAUDE.md, not in tool
descriptions. That layer is correct because it runs as system-prompt
injection, where exhortations belong. ctx_purgeis rewritten in PR #683 WS2 with carve-out headers
(DESTRUCTIVE,SCOPES,CONTRACT) allow-listed by the contract
test'sALLOWED_EXTRA_SECTIONS. The rewrite preserves Probe 4's
parameter-fidelity discipline (accurate DESTRUCTIVE signal + explicit
SCOPES + CONTRACT block) while still meeting the canonicalWHEN / WHEN NOT / RETURNS / EXAMPLEstructure.- New
ctx_*tools added in future PRs MUST cite this ADR in the PR
description and pass the contract test.
Alternatives considered
- Status quo (no style policy). Rejected โ PR #654 evidence shows
organic drift directly causes user-visible bugs (Opus 4.6 capitulation). - Single hortatory voice across all descriptions. Rejected โ Probe 4
evidence: heavy framing helpsctx_purgeand hurtsctx_execute.
One-size-fits-all is empirically wrong. - Per-tool author discretion. Rejected โ that's what we already had,
and it produced the bug. - Rewrite all 11 in one PR. Rejected โ large diff, hard to revert,
blocks on style debates, and would regressctx_purgeper Probe 4.
PR #683 explicitly defersctx_purge.
References
TOOL-DESCRIPTIONS-AUDIT.md(ยง3 audit table, ยง5 probe evidence, ยง6
verbatim rewrites)GRILL-Q1-VERDICT.md(SESSION STATE drop rationale)- ADR-0003 โ Routing deny reasons MUST distinguish redirect from
restriction (sibling decision, also from PR #683)
File: docs/adr/0003-routing-deny-reasons.md
ADR-0003 โ Routing deny reasons: redirect โ restriction
- Status: Accepted โ amended 2026-05-24 (PR #683 follow-up) to drop the
"NOT a network restriction"negation prescription; see ยงAmendment. - Date: 2026-05-24
- PR: #683 (substitutes #654)
- Motivating bug: kerneltoast / @noctivoro / Mert reproduced on Opus 4.6
- Reviewers: owner Mert
Context
hooks/core/routing.mjs returns deny reasons for intercepted Bash, Read,
Grep, and WebFetch calls. These reasons are displayed to the agent at
runtime and shape the agent's next-action decision. PR #654
(kerneltoast) reproduced that the bare word "blocked" in WebFetch's
deny reason ("WebFetch blocked") was misread by Opus 4.6 as a
network / security restriction, causing the agent to capitulate to
training data instead of using the redirected tool.
The intent of the routing layer is to redirect the agent to a
context-efficient alternative (ctx_fetch_and_index for WebFetch,ctx_execute for large-output Bash). It is NOT a security gate, and
its denial text MUST NOT read like one.
There is a real, separate set of denials in routing.mjs that ARE
security restrictions: the deny-pattern check (curl to private IPs,
sensitive-path reads, etc.). Those denials are correct to read like
restrictions โ they are restrictions.
The bug PR #654 caught was that a single deny-reason string in CASE A
was using the vocabulary of CASE B. The fix is to formalize the
distinction so any future hook author cannot make the same mistake.
Decision
Routing deny reasons MUST distinguish two cases:
CASE A โ Routing redirect
The action is supported, via a different tool, for context-window or
efficiency reasons.
- Opening verb:
"redirected to <ctx_tool>"โ affirmative routing
intent, no negation, no org-rationale (the agent's job is to act on the
redirect, not to audit policy). - MUST affirm capability:
"<ctx_tool> has full network access"
(positive frame of the same signal the oldNOT a network restriction
parenthetical tried to deliver โ and the same signal thefor context-window efficiencyrationale prescription also tried to
deliver before the 2026-05-24 second amendment). - MUST specify: the alternative tool to use, by name, as an
imperative call (e.g.Call ctx_fetch_and_index(url, source) now). - MUST end with: a positive imperative retry hint โ
"Retry the same call on a transient DNS error (EAI_AGAIN, ETIMEDOUT, ENETUNREACH)". - MUST NOT contain: bare-NOT negations (
NOT a network restriction,Do NOT retry with curl, etc.). See ยงAmendment below for the
empirical rationale. - MUST NOT contain: org-rationale prefaces (
for context-window efficiency,for performance, etc.). The redirect verb + the
capability affirmation already carry the full action signal; rationale
is metadata, not action input. See ยงSecond amendment below.
The word BLOCKED MUST NOT appear bare in CASE A. It is reserved for
true policy denial (CASE B) where the agent's correct response IS to
stop and inform the user.
CASE B โ True security / policy restriction
The action is denied per deny pattern, security gate, or unsupported
sandbox capability.
- Opening verb: "denied" or "blocked by security policy"
- MUST cite: the pattern or rule violated
- MAY suggest: a safe alternative
Amendment (PR #683 follow-up, 2026-05-24)
The original CASE A rubric required the parenthetical "this is NOT a network / security restriction". Mert flagged on review that this
prescription itself violates ADR-0002 rubric #2 (affirmative beats
negation): the bare-NOT construct primes the very frame it tries to
deny (ironic process theory). TOOL-DESCRIPTIONS-AUDIT.md ยง2 Probe 3
already documented this โ the NOT-parenthetical regressed Haiku
capitulation rate from 0/6 โ 2/6 vs the original "blocked" wording.
PR #654 fixed the headline word (blocked โ redirected) but kept the
sibling negation in the very next clause. PR #683 follow-up
(this amendment) eradicates the negation construct entirely:
- Replace
"(context-window optimization, NOT a network restriction)"
with the affirmative sentence"<ctx_tool> has full network access."
(originally also prescribed a"for context-window efficiency"opening
preface โ see ยงSecond amendment below for why that prescription was
dropped). - Replace
"Do NOT retry with curl/wget"with positive"Retry the same call on a transient DNS error (...)"โ the
affirmative retry hint IS the next-action signal; the prohibition was
redundant once the routing is correct.
Second amendment (PR #683 mid-review, 2026-05-24)
Mert flagged on review of the first amendment: the prescribed opening"redirected to <ctx_tool> for context-window efficiency" itself
carries org-rationale that the agent does not need to act. The agent's
job is to (a) understand a redirect happened, (b) make the correct next
call, (c) know it has the capability to do so, (d) know when to retry.
"For X reason" is post-hoc justification โ pure prompt overhead.
Compare to HTTP 301 Moved Permanently โ the response carries Location: <new-url> and the client uses it. The server never appends"for SEO efficiency" or "for caching strategy". The redirect verb
- the new target are the entire action signal.
The capability affirmation ("<ctx_tool> has full network access")
already delivers the substantive content the rationale was trying to
carry โ that this is a routing optimization, not a capability denial.
The rationale preface was double-encoding that signal.
The four CASE A sites in hooks/core/routing.mjs (L707 curl/wget,
L738 inline HTTP, L751 build tool, L804 WebFetch) all conform to the
second-amended rubric after this PR. A contract test intests/core/server.test.ts (ADR-0003 CASE A: routing.mjs redirect deny reasons) locks the rule: every CASE A string MUST open with
"redirected", MUST NOT contain bare uppercase BLOCKED, MUST name at
least one ctx_* alternative, MUST NOT contain "NOT a network",
MUST NOT contain "Do NOT retry", MUST NOT contain"for context-window efficiency" (or sibling org-rationale prefaces).
Consequences
- PR #654's wording fix (
"blocked"โ"redirected") becomes formal
policy. - PR #683 already lands the wording change at
hooks/core/routing.mjs:804and adds theEAI_AGAIN | ETIMEDOUT | ETIMEOUT | ENETUNREACH | EPERMtransient-DNS retry hint to bothrouting.mjs(WebFetch denial) andsrc/server.ts:2783-2795
(ctx_fetch_and_indexsubprocess fetch failure) so the two surfaces
speak with one voice. - Existing
routing.mjsdeny reasons audited for CASE A / CASE B
classification:- L707 (curl / wget redirect) โ CASE A
- L738 (inline HTTP redirect) โ CASE A
- L803 (WebFetch redirect) โ CASE A
- L652, L844, L862, L873, L894 (security deny patterns) โ CASE B
- Test substring expectations in
tests/hooks/*updated where they
asserted the literal word"blocked"for CASE A paths. - A future contract test on
routing.mjsdeny reasons (similar to PR
#683'stool description style contract) is out of scope here but
recommended as a follow-up โ the rule is already mechanically
checkable.
Alternatives considered
- Keep "blocked" everywhere; document the convention. Rejected โ
documentation doesn't change LLM behaviour, the wording does. PR
#654's empirical reproduction is the disqualifying evidence. - Remove the denial path entirely; just silently invoke the
alternative tool. Rejected โ the agent needs to know its tool call
was rerouted (for telemetry, for the user-visible audit trail, and so
it can adjust its plan). - Use the same wording in both cases, distinguish by exit code.
Rejected โ the agent reads the prose, not the exit code, when
deciding whether to retry or capitulate.
References
- PR #654 (kerneltoast) โ original bug report and reproduction
- PR #683 โ implementation + ADR
- ADR-0002 โ Tool description voice and structure (companion ADR,
same PR) TOOL-DESCRIPTIONS-AUDIT.mdยง2 โ PR #654 verdict and probe evidence
File: docs/adr/0004-stats-strict-compression-formula.md
ADR-0004 โ Stats display uses strict-compression formula
- Status: Accepted
- Date: 2026-05-24
- PR: #685 (v1.0.148 hotfix)
- Supersedes: v1.0.134 SLICE B (incidental fix in commit
ce62275) - Acknowledgement: 7-agent EM ops audit (Git Archaeologist, DB Architect,
Math Engineer, QA Engineer, PO/UX Engineer, Edge Case Engineer,
Architect) produced the converged verdict ratified here.
Context
The per-conversation Section 1 Without context-mode / With context-mode
bar in ctx_stats quietly drifted from "honest compression ratio" to
"infrastructure-size accounting" across two unrelated bug cascades:
v1.0.134 SLICE B (
analytics.ts:1991-1993) โ A tactical fix for a
degenerate-100% display bug. WhenbytesReturned == 0in fresh
sessions, the originalpct = 1 - max(1, returned) / (avoided + returned)
collapsed to ~100%, even with zero avoided bytes. SLICE B addedeventDataBytesto both sides of the ratio to prevent the
degenerate bar:textWithout = bytesAvoided + bytesReturned + eventDataBytes With = max(1, bytesReturned + eventDataBytes)The commit message (
ce62275, 2026-05-15) named this a
"bar ratio degenerate fix" โ it was an UX patch, not a designed metric
semantic. Git archaeology confirmsrule_contentduplication
(the actual cost driver) was never considered.v1.0.148 Bug A+C+D+E+F cascade (PR #685) โ schema migration +
per-conversation aggregator fixes finally let the formula see realbytesAvoideddata after years of silent under-attribution. With
the real signal flowing, SLICE B's eventDataBytes-on-both-sides
formula started reporting ~56% on conversations the user knew
should be 95%+.
The reporter's machine produced empirical evidence:
bytesAvoided= 2,898 KB (Bash/Read redirect savings + sandbox PID bursts)bytesReturned= 140 KB (printed ctx_* output)eventDataBytes= 2,136 KB (84% of which is 496 duplicate copies of the
same CLAUDE.md captured by SessionStart hooks across resume cycles โ
schema'sdata_hashdedup column is populated but unused by the formula)
Under SLICE B: display says 56% kept out.
Under strict compression (this ADR): display says 95.4% kept out.
The 49-percentage-point gap is the under-attribution SLICE B introduced.
Decision
The per-conversation Section 1 bar MUST use the strict-compression formula:
if (bytesAvoided + bytesReturned == 0) {
// Empty state โ no measurable redirect activity yet.
// Do NOT draw a degenerate bar. Emit one honest hint line:
"No measurable redirect activity captured yet โ bars will appear once
context-mode diverts its first payload."
} else {
Without = bytesAvoided + bytesReturned
With = max(1, bytesReturned)
pct = (1 - With / Without) * 100
}eventDataBytes is EXCLUDED from both sides. Hook-captured payload
bytes are written to SessionDB for the knowledge base. They are
analytics infrastructure, not bytes that ever entered the model's
context window. Rendering them in Section 1 conflates two distinct
quantities and produces a misleading number.
eventDataBytes MAY still be surfaced in Section 2 (captures count,
"1,000 things โ files, errors, decisions, agent runs") where it
correctly represents what the hook layer recorded.
The lifetime Section 3 / Section 4 totals (14.7 MB kept out across 200 projects) are unchanged by this ADR โ they aggregatebytesAvoided + eventDataBytes + snapshotBytes and the user
expectation for the lifetime tier has historically been "all the
bytes context-mode kept in storage", which is correct for those
sections. Only the per-conversation % bar's semantic is corrected.
Consequences
The displayed Section 1 percentage will jump from ~56% to ~95%
for existing users on firstctx_statscall after v1.0.148.
This is a metric semantic change, not data loss; lifetime
numbers and capture counts remain identical to v1.0.147.Empty-state handling is explicit. Fresh sessions with no
redirect activity see a one-line hint instead of a degenerate0%or100%bar. SLICE B's symptom is eliminated at the
source, not papered over.The
data_hashdedup column is no longer load-bearing for
correctness of the Section 1 display. Dedup was one candidate
fix in the EM verdict tree (Option B, 86%); strict compression
(this ADR) is the correct fix because the rule_content
duplication problem only matters if you're countingeventDataBytesin the first place โ and we are not.Four fixture tests in
tests/analytics/format-report*.test.ts
are updated to assert the new strict-compression semantic.
Thev1.0.134 SLICE Bdescribe block is renamed tov1.0.148 Bug G โ strict-compression formulaand now pins:
(a) the empty-state hint, (b) honest mixed-case percentage,
(c) honest 100% when onlybytesAvoidedexists.README and release notes updated to reflect the new
per-conversation percentage range. Headline marketing claims
that previously cited ~98% lifetime savings remain valid under
the lifetime formula; new per-conversation headline aligns
with the strict-compression ratio.ADR-0001 (multi-writer) is preserved โ this ADR changes a
read-side formula only. No schema additions, no locks, no
EXCLUSIVE pragma. SQLite WAL + busy_timeout invariants remain
intact per ADR-0001.
Pre-fix vs post-fix on reporter's data
| Metric | v1.0.147 (broken) | v1.0.148 + SLICE B | v1.0.148 + this ADR |
|---|---|---|---|
| Without | 158 KB | 5,177 KB | 3,038 KB |
| With | 158 KB | 2,279 KB | 140 KB |
| % kept out | 0% (identity) | 56% (SLICE B incidental) | 95.4% |
| Runtime multiplier | 1ร | 2ร | 22ร |
| Lifetime headline | 14.7 MB โ | 14.7 MB โ | 14.7 MB โ |
The 22ร multiplier represents the actual context-window runway
extension this conversation got from context-mode's redirects โ
the metric the user intuitively expected to see.
File: docs/adapters/openclaw.md
OpenClaw Adapter
context-mode plugin for the OpenClaw gateway, targeting Pi Agent sessions.
Overview
OpenClaw is the gateway/platform that manages agent sessions, extensions, and tool routing. Pi Agent is OpenClaw's coding agent โ it runs within OpenClaw and provides Read, Write, Edit, and Bash tools for software development tasks.
The context-mode adapter hooks into Pi Agent sessions specifically, intercepting tool calls to route data-heavy operations through the sandbox and tracking session events for compaction recovery.
Supported Configurations
- Pi Agent sessions with coding tools (Read/Write/Edit/Bash) โ fully supported.
- Custom agents with coding tools โ may work but are untested. The adapter relies on tool names matching Pi Agent's conventions.
Installation
Quick install
npm run install:openclawThis runs scripts/install-openclaw-plugin.sh, which handles building, extension setup, runtime registration, and gateway restart.
Prerequisites
- Node.js must be in PATH (required for the build and registration steps)
- OpenClaw must have been started once โ the script needs
openclaw.json, which OpenClaw creates on first launch OPENCLAW_STATE_DIRmust point to your OpenClaw state directory (default:/openclaw). Pass it as an argument:npm run install:openclaw -- /path/to/state
Manual install
For advanced users or custom setups:
bash scripts/install-openclaw-plugin.sh [OPENCLAW_STATE_DIR]See scripts/install-openclaw-plugin.sh for details.
Troubleshooting
"openclaw.json not found"
OpenClaw creates this file on first launch. Start OpenClaw once (openclaw gateway start), then re-run the install script. This is the most common issue for users who install context-mode before ever starting OpenClaw.
"OPENCLAW_STATE_DIR (/path) does not exist. Is OpenClaw installed?"
The state directory doesn't exist at the expected path. If you installed OpenClaw via npm (not git clone), check where it stores state โ common locations are ~/.openclaw or /openclaw. Pass the correct path: npm run install:openclaw -- /path/to/state.
Plugin installed but not loading
Clear the jiti cache (rm -f /tmp/jiti/context-mode-*.cjs) and restart the gateway. If the issue persists, verify the plugin appears in openclaw plugins list.
Plugin loads but ctx_* tools are missing from the agent's tool list
The plugin registers its hooks via api.on(...) / api.registerCommand(...), but the agent-callable ctx_* tools live in the MCP server (server.bundle.mjs). OpenClaw surfaces them by spawning the server as an MCP sidecar declared in mcp.servers.context-mode. The install script writes this entry automatically (step 5); if you configured OpenClaw manually, verify with openclaw mcp list and add it if missing:
openclaw mcp set context-mode \
"{\"command\":\"node\",\"args\":[\"/absolute/path/to/context-mode/server.bundle.mjs\"]}"
openclaw gateway restartAfter the restart, the agent's tool inventory should include context-mode__ctx_execute, context-mode__ctx_search, context-mode__ctx_fetch_and_index, and the rest of the ctx_* surface (OpenClaw prefixes MCP-sourced tools with the server name).
Hook Registration
The adapter uses two different registration APIs, matching OpenClaw's internal architecture:
api.on()for lifecycle and tool hooks:session_start,before_tool_call,after_tool_call,before_compaction,after_compaction,before_prompt_build,before_model_resolve. These are typed event emitters with structured payloads.api.registerHook()for command hooks:command:new,command:reset,command:stop. These use colon-delimited names and the generic hook registration system.
Using the wrong API (e.g., api.registerHook("before_tool_call", ...)) registers silently but the hook never fires. This distinction is critical.
Synchronous `register()`
OpenClaw silently discards the return value of register(). If register() is async, all hooks registered inside it are lost. The adapter uses the initPromise pattern:
register(api): void {
const initPromise = (async () => { /* async setup */ })();
api.on("after_tool_call", async (e) => {
await initPromise;
// handle event
});
}Session Continuity
| Hook | Method | Status |
|---|---|---|
after_tool_call |
api.on() |
Working |
before_compaction |
api.on() |
Working |
session_start |
api.on() |
Working |
command:new |
api.registerHook() |
Working |
command:reset |
api.registerHook() |
Working |
command:stop |
api.registerHook() |
Working |
Graceful Degradation
If compaction hooks fail to fire (e.g., on older OpenClaw versions), the adapter falls back to DB snapshot reconstruction โ rebuilding session state from the events already persisted in SQLite by after_tool_call. This produces a less precise snapshot than the PreCompact path but preserves critical state (active files, tasks, errors).
Previously Known Upstream Issues
Both issues below have been resolved in upstream OpenClaw:
- #4967 โ Compaction hooks not firing. Closed as duplicate of #3728; fix merged.
- #5513 โ
api.on()hooks not invoked for tool lifecycle events. Fixed in PR #9761.
Minimum Version
Required: OpenClaw >2026.1.29
This is the first release that includes the api.on() fix from PR #9761, which shipped on 2026-01-29.
What breaks on older versions: Lifecycle hooks registered via api.on() โ including before_compaction, after_compaction, session_start, and tool interception hooks โ may silently fail to fire.
Graceful degradation: If compaction hooks don't fire, the adapter falls back to DB snapshot reconstruction, rebuilding session state from events already persisted by after_tool_call. This produces a less precise snapshot than the PreCompact path but preserves critical state (active files, tasks, errors). The adapter will not crash on older versions, but compaction recovery quality will be reduced.
Workspace Routing
The adapter includes a workspace router (src/adapters/openclaw/workspace-router.ts) that resolves project paths from Pi Agent session metadata, ensuring session databases and routing instructions are scoped per-workspace.
Key Files
| File | Purpose |
|---|---|
src/adapters/openclaw/plugin.ts |
Main plugin entry (sync register, initPromise pattern) |
src/adapters/openclaw/workspace-router.ts |
Workspace path resolution for session scoping |
.openclaw-plugin/ |
Plugin manifest (index.ts, openclaw.plugin.json, package.json) |
scripts/install-openclaw-plugin.sh |
One-shot installer |
File: docs/jetbrains-copilot.md
JetBrains Copilot Setup
Setup guide for using context-mode with JetBrains IDEs (IntelliJ IDEA, WebStorm, PyCharm, etc.) via the GitHub Copilot plugin.
Prerequisites
- Node.js 18+ (
node --version) - JetBrains IDE โ any JetBrains IDE (IntelliJ IDEA, WebStorm, PyCharm, GoLand, Rider, CLion, etc.)
- GitHub Copilot plugin v1.5.57+ โ install from Settings > Plugins > Marketplace, search "GitHub Copilot"
MCP Setup
JetBrains configures MCP servers via the Settings UI, not a file.
- Open your JetBrains IDE.
- Go to Settings > Tools > AI Assistant > Model Context Protocol (MCP).
- Click Add Server and configure:
- Name:
context-mode - Command:
npx - Args:
-y context-mode
- Name:
- Click OK to save.
Alternatively, you can use a global install (npm install context-mode) and set the command to context-mode with no args.
Example MCP config (for reference): configs/jetbrains-copilot/mcp.json
Hook Installation
Install hooks using the automated setup command:
npx context-mode@latest setup --adapter jetbrains-copilotThis creates .github/hooks/context-mode.json in your project with the following hook configuration:
{
"hooks": {
"PreToolUse": [
{ "type": "command", "command": "context-mode hook jetbrains-copilot pretooluse" }
],
"PostToolUse": [
{ "type": "command", "command": "context-mode hook jetbrains-copilot posttooluse" }
],
"PreCompact": [
{ "type": "command", "command": "context-mode hook jetbrains-copilot precompact" }
],
"SessionStart": [
{ "type": "command", "command": "context-mode hook jetbrains-copilot sessionstart" }
]
}
}Full hook config reference: configs/jetbrains-copilot/hooks.json
Upgrade
Update context-mode to the latest version:
context-mode upgradeOr from within a Copilot chat session, type ctx upgrade.
Verification
Run the diagnostics to verify everything is working:
context-mode doctorOr from within a Copilot chat session, type ctx doctor.
All checks should show [x]. The doctor validates runtimes, hooks, FTS5, and MCP registration.
You can also verify context savings by typing ctx stats in a Copilot chat session.
Troubleshooting
MCP server not connecting
- Ensure Node.js 18+ is in your PATH.
- Restart the JetBrains IDE after adding the MCP server.
- Check Settings > Tools > AI Assistant > MCP and confirm "context-mode" shows a green status indicator.
Hooks not firing
- Verify
.github/hooks/context-mode.jsonexists in your project root. - JetBrains Copilot reads hooks from
.github/hooks/โ the same location as VS Code Copilot. - Re-run
npx context-mode@latest setup --adapter jetbrains-copilotto regenerate the hook config.
"context-mode: command not found"
- Install globally:
npm install context-mode - Verify:
which context-modeshould return a path. - If using
npx, ensure npx is in your IDE's PATH.
Tools appear but routing is not enforced
- Hooks enforce routing programmatically. Without hooks, the model can still use context-mode tools but won't be redirected to prefer them.
- Ensure the hook config file is in
.github/hooks/context-mode.json(not.github/hooks.json).
Session continuity not working
- Verify all four hooks (PreToolUse, PostToolUse, PreCompact, SessionStart) are configured.
- Run
ctx doctorto check hook registration status.
File: docs/platform-support.md
Platform Support Matrix
This document provides a comprehensive comparison of all platforms supported by context-mode, including their hook paradigms, capabilities, configuration, and known limitations.
Overview
context-mode supports 17 client platforms, plus the OpenClaw gateway integration, across three hook paradigms:
| Paradigm | Platforms |
|---|---|
| JSON stdin/stdout | Claude Code, Gemini CLI, VS Code Copilot, JetBrains Copilot, GitHub Copilot CLI, Cursor, Codex CLI, Qwen Code, Kimi Code, Antigravity CLI (agy), Kiro |
| TS Plugin | OpenCode, KiloCode, OpenClaw |
| MCP-only | Antigravity, Zed, Pi, OMP (Oh My Pi) |
The MCP server layer is 100% portable and needs no adapter. Only the hook layer requires platform-specific adapters.
Prerequisites
All platforms (except Claude Code plugin install) require a global install:
npm install context-modeThis puts the context-mode binary in PATH, which is required for:
- MCP server:
"command": "context-mode"(replaces ephemeralnpx -y context-mode) - Hook dispatcher:
context-mode hook <event>(replacesnode ./node_modules/...paths) - Utility commands:
context-mode doctor,context-mode upgrade - Persistent upgrades:
ctx-upgradeupdates the global binary in-place
Main Comparison Table
| Feature | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Kimi Code | Antigravity | Antigravity CLI (agy) |
Kiro | Zed | Pi | OMP |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Paradigm | json-stdio | json-stdio | json-stdio | json-stdio | json-stdio | json-stdio | json-stdio | ts-plugin | ts-plugin | ts-plugin | json-stdio | json-stdio | mcp-only | json-stdio | json-stdio | mcp-only | mcp-only | mcp-only |
| PreToolUse equivalent | PreToolUse |
PreToolUse |
BeforeTool |
PreToolUse |
PreToolUse |
preToolUse |
preToolUse |
tool.execute.before |
tool.execute.before |
tool_call:before |
PreToolUse |
PreToolUse |
-- | PreToolUse (bounded) |
preToolUse |
-- | -- | -- |
| PostToolUse equivalent | PostToolUse |
PostToolUse |
AfterTool |
PostToolUse |
PostToolUse |
postToolUse |
postToolUse |
tool.execute.after |
tool.execute.after |
tool_call:after |
PostToolUse |
PostToolUse |
-- | PostToolUse (capture-only) |
postToolUse |
-- | -- | -- |
| PreCompact equivalent | PreCompact |
PreCompact |
PreCompress |
PreCompact |
PreCompact |
preCompact |
-- | experimental.session.compacting |
experimental.session.compacting |
registerContextEngine |
-- | PreCompact |
-- | -- | -- | -- | -- | -- |
| SessionStart | SessionStart |
SessionStart |
SessionStart |
SessionStart |
SessionStart |
sessionStart |
-- (buggy in Cursor) | -- | -- | command:new |
SessionStart |
SessionStart |
-- | -- | -- | -- | -- | -- |
| Stop equivalent | -- | -- | -- | Stop |
Stop |
agentStop |
stop |
-- | -- | -- | Stop |
Stop |
-- | Stop (best-effort, unverified on agy 1.0.6) |
-- | -- | -- | -- |
| Can modify args | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | -- | -- | -- | -- | -- | -- |
| Can modify output | Yes | Yes | Yes | Yes | Yes | No | No | Yes (caveat) | Yes (caveat) | No | No | Yes | -- | -- | -- | -- | -- | -- |
| Can inject session context | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | -- | Yes | Yes | Yes | -- | -- | -- | -- | -- | -- |
| Can block tools | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes (throw) | Yes (throw) | Yes | Yes | Yes | -- | Bounded | Yes | -- | -- | -- |
| Config location | ~/.claude/settings.json |
~/.qwen/settings.json |
~/.gemini/settings.json |
.github/hooks/*.json |
.github/hooks/*.json |
~/.copilot/hooks/context-mode.json + ~/.copilot/mcp-config.json |
.cursor/hooks.json or ~/.cursor/hooks.json |
opencode.json |
kilo.json |
openclaw.json |
~/.codex/hooks.json + ~/.codex/config.toml |
~/.kimi-code/config.toml |
~/.gemini/antigravity/mcp_config.json |
~/.gemini/config/mcp_config.json + ~/.gemini/config/hooks.json |
~/.kiro/settings/mcp.json |
~/.config/zed/settings.json |
~/.pi/settings.json |
~/.omp/agent/mcp_config.json |
| Session ID field | session_id |
session_id |
session_id |
sessionId (camelCase) |
sessionId (camelCase) |
session_id (snake_case; sessionId defensive fallback) |
conversation_id |
sessionID (camelCase) |
sessionID (camelCase) |
pid-${ppid} fallback |
N/A | session_id |
N/A | conversationId (unverified) |
pid-${ppid} fallback |
N/A | N/A | N/A |
| Project dir env | CLAUDE_PROJECT_DIR |
QWEN_PROJECT_DIR |
GEMINI_PROJECT_DIR |
CLAUDE_PROJECT_DIR |
CLAUDE_PROJECT_DIR |
stdin cwd |
stdin workspace_roots |
ctx.directory (plugin init) |
ctx.directory (plugin init) |
process.cwd() |
N/A | stdin cwd |
N/A | stdin workspace.current_dir (refs-backed; workspacePaths[0] fallback) |
stdin cwd |
N/A | N/A | OMP_PROCESSING_AGENT_DIR |
| MCP/tool naming | mcp__server__tool |
mcp__server__tool |
mcp__server__tool |
f1e_ prefix |
f1e_ prefix |
mcp__server__tool |
MCP:<tool> in hook payloads |
native ctx_* plugin tools |
native ctx_* plugin tools |
native ctx_* plugin tools |
mcp__server__tool |
mcp__context-mode__tool |
mcp__server__tool |
context-mode/<tool> |
mcp__server__tool |
mcp__server__tool |
mcp__server__tool |
mcp__server__tool |
| Hook command format | context-mode hook claude-code <event> |
context-mode hook qwen-code <event> |
context-mode hook gemini-cli <event> |
context-mode hook vscode-copilot <event> |
context-mode hook jetbrains-copilot <event> |
context-mode hook copilot-cli <event> |
context-mode hook cursor <event> |
TS plugin (no command) | TS plugin (no command) | TS plugin (no command) | context-mode hook codex <event> |
context-mode hook kimi <event> |
N/A | context-mode hook antigravity-cli <event> |
context-mode hook kiro <event> |
N/A | N/A | N/A |
| Hook registration | settings.json hooks object | settings.json hooks object | settings.json hooks object | .github/hooks/*.json |
.github/hooks/*.json |
~/.copilot/hooks/context-mode.json (version: 1) |
hooks.json native hook arrays |
opencode.json plugin array | kilo.json plugin array | openclaw.json plugins.entries |
~/.codex/hooks.json |
config.toml hooks array |
N/A | plugin root hooks.json (PreToolUse, PostToolUse, Stop; bundle mirrors hooks/hooks.json for agy validate/install) |
Kiro CLI hooks (JSON stdin) | N/A | N/A | N/A |
| MCP server command | context-mode (or plugin auto) |
context-mode |
context-mode |
context-mode |
context-mode |
context-mode |
context-mode |
N/A (native plugin tools) | N/A (native plugin tools) | N/A (native plugin tools) | context-mode |
context-mode |
context-mode |
context-mode |
context-mode |
context-mode |
context-mode |
context-mode |
| Plugin distribution | Claude plugin registry | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | npm global | agy plugin (npm global) | npm global | npm global | npm global | npm global |
| Session dir | ~/.claude/context-mode/sessions/ |
~/.qwen/context-mode/sessions/ |
~/.gemini/context-mode/sessions/ |
.github/context-mode/sessions/ or ~/.vscode/context-mode/sessions/ |
.github/context-mode/sessions/ |
~/.copilot/context-mode/sessions/ |
~/.cursor/context-mode/sessions/ |
~/.config/opencode/context-mode/sessions/ |
~/.config/kilo/context-mode/sessions/ |
~/.openclaw/context-mode/sessions/ |
~/.codex/context-mode/sessions/ |
~/.kimi-code/context-mode/sessions/ |
~/.gemini/context-mode/sessions/ |
~/.gemini/context-mode/sessions/ |
~/.kiro/context-mode/sessions/ |
~/.config/zed/context-mode/sessions/ |
~/.pi/context-mode/sessions/ |
~/.omp/context-mode/sessions/ |
Legend
- Yes = Fully supported
- -- = Not supported
- (caveat) = Supported with known issues
Platform Details
Claude Code
Status: Fully supported (primary platform)
Hook Paradigm: JSON stdin/stdout
Claude Code is the primary platform for context-mode. All hooks communicate via JSON on stdin/stdout. The adapter reads raw JSON input, normalizes it into platform-agnostic events, and formats responses back into Claude Code's expected output format.
Hook Names:
PreToolUse-- fires before a tool is executedPostToolUse-- fires after a tool completesPreCompact-- fires before context compactionSessionStart-- fires when a session starts, resumes, or compactsUserPromptSubmit-- fires when user submits a promptStop-- fires when the assistant turn is about to end
Blocking: permissionDecision: "deny" in response JSON
Arg Modification: updatedInput field at top level of response
Output Modification: updatedMCPToolOutput for MCP tools, additionalContext for appending
Session ID Extraction Priority:
- UUID from
transcript_pathfield session_idfieldCLAUDE_SESSION_IDenvironment variable- Parent process ID fallback
Hook Commands:
context-mode hook claude-code pretooluse
context-mode hook claude-code posttooluse
context-mode hook claude-code precompact
context-mode hook claude-code sessionstart
context-mode hook claude-code userpromptsubmitKnown Issues: None significant.
Gemini CLI
Status: Fully supported
Hook Paradigm: JSON stdin/stdout
Gemini CLI uses the same JSON stdin/stdout paradigm as Claude Code but with different hook names and response format.
Hook Names:
BeforeTool-- equivalent to PreToolUseAfterTool-- equivalent to PostToolUsePreCompress-- equivalent to PreCompact (advisory only, async, cannot block)SessionStart-- fires when a session starts
Blocking: decision: "deny" in response (NOT permissionDecision)
Arg Modification: hookSpecificOutput.tool_input (merged with original, not updatedInput)
Output Modification: decision: "deny" + reason replaces output; hookSpecificOutput.additionalContext appends
Environment Variables:
GEMINI_PROJECT_DIR-- primary project directoryCLAUDE_PROJECT_DIR-- alias (also works)
Hook Commands:
context-mode hook gemini-cli beforetool
context-mode hook gemini-cli aftertool
context-mode hook gemini-cli precompress
context-mode hook gemini-cli sessionstartKnown Issues / Caveats:
PreCompressis advisory only (async, cannot block)- No
decision: "ask"support - Hooks don't fire for subagents yet
OpenCode
Status: Fully supported
Hook Paradigm: TS Plugin
OpenCode uses a TypeScript plugin paradigm instead of JSON stdin/stdout. Hooks and the 11 ctx_* tools are registered via the plugin array in opencode.json; no separate mcp block or stdio MCP child is required.
Hook Names:
tool.execute.before-- equivalent to PreToolUsetool.execute.after-- equivalent to PostToolUseexperimental.session.compacting-- equivalent to PreCompact (experimental)experimental.chat.system.transform-- SessionStart-equivalent (cross-session resume injection)
Blocking: throw Error in tool.execute.before handler
Arg Modification: output.args mutation
Output Modification: output.output mutation (TUI bug for bash, see issue #13575)
Session ID: input.sessionID (camelCase, note the uppercase ID)
Project Directory: Available via ctx.directory in plugin init, not via environment variable
Desktop markers: OpenCode desktop shells also export OPENCODE_CLIENT=desktop and OPENCODE_TERMINAL=1; context-mode treats those as OpenCode identity signals when the CLI markers are absent.
Configuration:
opencode.jsonor.opencode/opencode.json- Plugin registered in the
pluginarray with npm package names ctx_*tools are native plugin tools, notmcp__server__toolcalls- KiloCode uses the same plugin path via
kilo.json;context-mode upgraderemoves stalemcp.context-modeentries for both hosts while preserving other MCP servers
Cross-session resume:
When OpenCode triggers experimental.session.compacting (auto on context overflow OR manual /compact), context-mode saves a snapshot to its per-project SQLite store. The NEXT new session in the same project โ typically after Ctrl+D then re-running opencode, or starting a fresh chat โ claims that snapshot via experimental.chat.system.transform and prepends it to system[1] (preserves OpenCode's [header, body] cache fold). The current session never claims its OWN snapshot back (self-injection guard, v1.0.106). To verify the injection landed, run with OPENCODE_DEBUG=1 and grep for <!-- context-mode v in the system prompt โ that's the visible marker.
Known Issues / Caveats:
- SessionStart is broken (issue #14808, no hook issue #5409) โ we use
experimental.chat.system.transformas a surrogate - Output modification has TUI rendering bug for bash tool (issue #13575)
experimental.session.compactingis marked experimental and may change- No
canInjectSessionContextcapability - Resume snapshots are scoped per-project (DB sharded by SHA-256 of
ctx.directory); no cross-project bleed
Codex CLI
Status: Supported (MCP active, hooks require [features].hooks = true)
Hook Paradigm: JSON stdin/stdout
Codex CLI's Rust backend (codex-rs) includes a hook system using the same JSON stdin/stdout wire protocol as Claude Code. Hooks are configured via hooks.json.
Hook Names:
PreToolUse-- fires before a tool is executedPostToolUse-- fires after a tool completesPreCompact-- fires before context compaction on Codex builds that emit itSessionStart-- fires when a session starts, resumes, or clearsUserPromptSubmit-- fires when user submits a promptStop-- fires when agent turn ends (can continue with followup)
Blocking: permissionDecision: "deny" in hookSpecificOutput, or exit code 2
Arg Modification: NOT supported (updatedInput returns error)
Output Modification: NOT supported (updatedMCPToolOutput returns error)
Context Injection: additionalContext in hookSpecificOutput (PostToolUse, SessionStart only). PreToolUse does NOT support additionalContext โ the codex formatter handles this automatically (deny works, context/modify/ask responses are dropped).
Configuration:
- Hook config:
$CODEX_HOME/hooks.jsonor~/.codex/hooks.json(JSON format, same structure as Claude Code) - MCP config:
$CODEX_HOME/config.tomlor~/.codex/config.toml(TOML format,[mcp_servers]section) - Feature flags: use
[features].hooks(orcodex --enable hooks) if you need
to force hooks on. Prefer[features].hooks;[features].codex_hooksremains
accepted as a legacy alias in current Codex builds.
Hook Commands:
context-mode hook codex pretooluse
context-mode hook codex posttooluse
context-mode hook codex precompact
context-mode hook codex sessionstart
context-mode hook codex userpromptsubmit
context-mode hook codex stopKnown Issues / Caveats:
PreToolUse
additionalContextis unsupported โ context injection works via PostToolUse and SessionStart instead. The codex formatter handles this automatically (deny works, context is dropped). Source:codex-rs/hooks/src/engine/output_parser.rs:267.PreToolUse input rewriting still needs upstream
updatedInputsupport. Track: openai/codex#18491.PreCompact support is runtime-gated: context-mode configures it and treats a missing registration as a warning, because older Codex builds may not emit the event. The hook stores the resume snapshot out-of-band and SessionStart restores it.
Codex emits structured tool names such as
Bashandapply_patch; context-mode only normalizes legacy shell aliases.updatedInput and updatedMCPToolOutput are in the schema but NOT implemented
Default hook timeout: 600 seconds
Older context-mode releases used a
plugins/context-mode -> ..symlink shim
because Codex rejects the repository root ("./") as an empty local plugin
source path. On native Windows, Git can check that symlink out as a regular
file containing only.., which makescodex plugin add context-mode@context-mode
fail withmissing plugin.json. Current releases avoid this by declaring the
Codex marketplace plugin as a relative Git source (url: "./"), so Codex
materializes the installed marketplace root and finds.codex-plugin/plugin.json
without any symlink or junction.After installation succeeds, verify that Codex hooks are enabled in
%USERPROFILE%\.codex\config.toml:toml[features] hooks = trueSome Codex builds may also require
plugin_hooks = true. Without hook support,
the MCP tools can still work, but automatic session capture and persistent
memory may not record events.
Kimi Code
Status: Supported (JSON stdin/stdout hooks + MCP)
Hook Paradigm: JSON stdin/stdout
Kimi Code CLI uses the same JSON stdin/stdout wire protocol as Claude Code and Codex, configured via ~/.kimi-code/config.toml with [[hooks]] array tables. The key difference from Codex is that Kimi accepts additionalContext, updatedInput, and permissionDecision: "ask" in PreToolUse responses โ the codex formatter drops these, but the kimi formatter emits them fully.
Hook Names:
PreToolUseโ fires before a tool is executedPostToolUseโ fires after a tool completesPreCompactโ fires before context compactionSessionStartโ fires when a session starts or resumesUserPromptSubmitโ fires when user submits a prompt (payload isContentPart[])Stopโ fires when the agent turn ends
Blocking: permissionDecision: "deny" in hookSpecificOutput, or exit code 2
Arg Modification: updatedInput inside hookSpecificOutput wrapper
Output Modification: additionalContext inside hookSpecificOutput
Context Injection: additionalContext in hookSpecificOutput (works in all hook types)
Configuration:
- Hooks:
~/.kimi-code/config.toml([[hooks]]array tables) - MCP:
~/.kimi-code/mcp.json - Sessions:
~/.kimi-code/context-mode/sessions/
Hook Commands:
context-mode hook kimi pretooluse
context-mode hook kimi posttooluse
context-mode hook kimi precompact
context-mode hook kimi sessionstart
context-mode hook kimi userpromptsubmit
context-mode hook kimi stopKnown Issues / Caveats:
UserPromptSubmitsendspromptas aContentPart[]array; the kimi hook normalizes this to a string for downstream extractors.- SessionStart
additionalContextinjection is emitted but acceptance by the host is not documented in Kimi Code CLI docs (fails-open if unsupported).
Qwen Code
Status: Supported (MCP + hooks โ identical wire protocol to Claude Code)
Hook Paradigm: JSON stdin/stdout (same as Claude Code)
Qwen Code (by Alibaba/Qwen team) uses the exact same hook wire protocol as Claude Code, verified from source (hookRunner.ts, claude-converter.ts). Hooks are configured inside ~/.qwen/settings.json under the hooks key.
Hook Names: PreToolUse, PostToolUse, SessionStart, PreCompact, UserPromptSubmit (Qwen supports 12 events total, context-mode uses these 5)
Blocking: permissionDecision: "deny" or exit code 2
Arg Modification: updatedInput in response
Output Modification: updatedMCPToolOutput in response
Context Injection: additionalContext in response
Configuration:
- Settings + hooks:
~/.qwen/settings.json - MCP:
mcpServersin settings.json - Sessions:
~/.qwen/context-mode/sessions/
Detection: MCP clientInfo (qwen-cli-mcp-client-* pattern), QWEN_PROJECT_DIR env var, or ~/.qwen/ config dir.
Hook Commands:
context-mode hook qwen-code pretooluse
context-mode hook qwen-code posttooluse
context-mode hook qwen-code sessionstart
context-mode hook qwen-code precompact
context-mode hook qwen-code userpromptsubmitAntigravity
Status: MCP-only (no hooks)
Hook Paradigm: MCP-only
Google Antigravity is an AI-powered IDE by Google/DeepMind. It shares the ~/.gemini/ directory structure with Gemini CLI but uses a separate config path for MCP servers. Antigravity does not expose a public hook API โ only MCP integration is available.
Configuration:
~/.gemini/antigravity/mcp_config.json(JSON format)- MCP servers configured in
mcpServersobject
Detection:
- Auto-detected via MCP protocol handshake (
clientInfo.name: "antigravity-client") - Fallback:
CONTEXT_MODE_PLATFORM=antigravityenvironment variable override
Routing Instructions:
GEMINI.mdauto-written at project root on first MCP server startup- Antigravity reads
GEMINI.mdnatively (same filename as Gemini CLI, different content โ no hook references)
Capabilities:
- PreToolUse: --
- PostToolUse: --
- PreCompact: --
- SessionStart: --
- Can modify args: --
- Can modify output: --
- Can inject session context: --
Known Issues / Caveats:
- No hook support โ only routing instruction files for enforcement (~60% compliance)
- Shares
~/.gemini/directory with Gemini CLI โ session DB uses project hash to prevent collision - No verified Antigravity-specific environment variables exist
Sources:
- Config path: Gemini CLI Issue #16058
- MCP support: Antigravity MCP docs
- clientInfo: Apify MCP Client Capabilities Registry
Antigravity CLI (`agy`)
Status: Plugin โ MCP + routing rule + routing skill + bounded hooks
Hook Paradigm: MCP for tools + JSON stdin/stdout hooks
The standalone Antigravity CLI (agy) is the command-line companion to Google Antigravity. Unlike the Antigravity IDE, agy has a native plugin system (agy plugin install|import) and a hook surface (~/.gemini/config/hooks.json). context-mode ships as a first-class agy plugin (configs/antigravity-cli/) bundling the MCP server, a routing rule, a routing skill, and bounded PreToolUse/PostToolUse/Stop hooks. It shares the ~/.gemini/ session root with the rest of the Gemini family; agy reads its global MCP profile from ~/.gemini/config/mcp_config.json (not the IDE's ~/.gemini/antigravity/mcp_config.json).
Verified: agy 1.0.10 (Linux). The GitHub-subpath install requires agy โฅ 1.0.7 (subpath + branch resolution landed in 1.0.7 โ run agy update to upgrade). No agy hook event was added, removed, or renamed through 1.0.10, and the shared ~/.gemini/config/hooks.json location has been canonical since agy 1.0.8, so the bundle's PreToolUse/PostToolUse/Stop wiring is current.
Install:
npm install context-mode(the plugin's MCP server + hooks run thecontext-modebinary), thenagy plugin install https://github.com/mksglu/context-mode/tree/main/configs/antigravity-cli. agy clones the repo, resolves theconfigs/antigravity-clisubpath (with branch resolution), and registers the bundle's nativeplugin.json+mcp_config.json, routing rule, routing skill, and hooks into its plugin profile under~/.gemini/config/plugins/context-mode/. Ifctx_*tools don't appear after an upgrade, clear agy's stale tool-schema cache (~/.gemini/antigravity-cli/mcp/context-mode/) and restart agy (agy caches MCP schemas and doesn't refresh them).- Already on Claude Code:
agy plugin import claudecan import that existing Claude setup, but the native context-mode agy bundle above is the supported path for agy hooks. - MCP only: add context-mode to
~/.gemini/config/mcp_config.jsonundermcpServers({"command":"context-mode"}).
Detection:
- MCP protocol handshake (
clientInfo.name: "agy"/"antigravity-cli") - Config-dir markers for a bare shell:
~/.local/bin/agy,~/.gemini/antigravity-cli/, or~/.gemini/config/mcp_config.jsonโ probed before the generic~/.claude/~/.geminifallbacks so a gemini-cliโagy migrant is not mis-detected as Claude Code (#774) - Fallback:
CONTEXT_MODE_PLATFORM=antigravity-clioverride
Hook payload: the only refs-backed field is the working directory, read from workspace.current_dir โ an object field, per the upstream hook example (refs/platforms/antigravity-cli/examples/title/title.sh:10, examples/title/README.md:11). context-mode reads workspace.current_dir FIRST for the project dir, falling back to workspacePaths[0]. The remaining payload shape โ { conversationId, stepIdx, toolCall: { name, args }, error, workspacePaths: [..], transcriptPath } โ is empirically-derived/unverified (no upstream agy doc confirms it) and is treated as best-effort. The event name arrives via argv (set in hooks.json), and the hook CWD is ~/.gemini/config. context-mode maps these onto its routing/capture pipeline (workspace.current_dir/workspacePaths[0]โproject dir, conversationIdโsession id [unverified], run_commandโBash, view_fileโRead, grep_searchโGrep, list_dirโLS, read_url_contentโWebFetch, search_webโWebSearch).
Capabilities:
- PreToolUse: bounded blocking for mapped Bash/Read/Grep/WebFetch surfaces (
run_command,view_file,grep_search,web_fetch,read_url_content) - PostToolUse: capture-only (records executed tool calls into the session DB)
- Stop: best-effort capture-only session-end marker (registered, but not observed in agy
-pprobes) - PreCompact / SessionStart / PreInvocation / PostInvocation: -- (not wired)
- Can modify args / output / inject context: -- (not verified/used)
Known Issues / Caveats:
- Bounded hook enforcement. context-mode registers PreToolUse only for mapped high-flood tools with existing routing branches.
list_dirandsearch_webare normalized for PostToolUse capture but are not PreToolUse-routed. PreInvocationandPostInvocationare visible in agy's hook list but intentionally not registered; agy 1.0.6-pprobes did not emit them, and their payload/response semantics are not verified against context-mode's pipeline.- agy's
PostToolUsepayload carries no tool-output text, so byte-accounting for tool output is unavailable on this surface; the tool call + project + error state are still captured. - Shares
~/.gemini/with Gemini CLI and Antigravity โ session DB uses the project hash to prevent collisions. - Gemini function-calling tool exposure. agy exposes MCP tools as Gemini function declarations, and Gemini's API rejects JSON Schema
const/additionalPropertiesโ a rejected schema makes agy silently drop that tool from the model's function list (the agent then hand-rolls the tool via shell scripts instead of calling it). context-mode emits Gemini-safe tool schemas (constโenum,additionalPropertiesstripped) so thectx_*tools are exposed. agy also caches each server's tool schemas under~/.gemini/antigravity-cli/mcp/<server>/and does not refresh them on reconnect, so a cache captured by an older context-mode keeps the tools hidden โ delete~/.gemini/antigravity-cli/mcp/context-mode/and restart agy to force a re-fetch.
Kiro
Status: Supported (MCP + native PreToolUse/PostToolUse hooks)
Hook Paradigm: JSON stdin/stdout
Kiro is an AWS agentic IDE and CLI. It supports MCP servers via ~/.kiro/settings/mcp.json using the standard mcpServers JSON format. The Kiro CLI also fires preToolUse/postToolUse hooks over JSON stdin (exit code 2 blocking), which context-mode's kiro adapter implements for routing enforcement and tool-event capture. agentSpawn (the Kiro SessionStart equivalent) and stop are not yet wired, so session restore after compaction is not available.
Detection:
- Auto-detected via MCP protocol handshake (
clientInfo.name: "Kiro CLI")
Configuration:
- Global:
~/.kiro/settings/mcp.json(JSON format, standardmcpServersobject) - Project:
.kiro/settings/mcp.json
Routing Instructions:
KIRO.mdwritten at project root on first MCP server startup
Hook System:
preToolUse/postToolUsehooks via JSON stdin (implemented)- Blocking: exit code 2 (similar to Gemini CLI pattern)
agentSpawn(SessionStart equivalent) andstopare not yet wired
Hook Commands:
context-mode hook kiro pretooluse
context-mode hook kiro posttooluseBuilt-in Tools:
fs_read/read,fs_write/write,execute_bash/shell,use_aws/aws
Capabilities:
- PreToolUse: Yes
- PostToolUse: Yes
- PreCompact: --
- SessionStart: -- (
agentSpawnnot yet wired) - Can modify args: --
- Can modify output: --
- Can inject session context: -- (via
agentSpawn, not yet wired) - Can block tools: Yes (exit code 2)
Known Issues / Caveats:
agentSpawn(SessionStart) andstopare not yet wired, so session restore after compaction is unavailable โ copyKIRO.mdto the project root for session-start routing- Kiro IDE hooks use a UI-based "Run Command" shell action; stdin format unverified
Sources:
- clientInfo.name: Kiro GitHub Issue #5205
- MCP config: Kiro MCP Configuration docs
- CLI hooks: Kiro CLI Hooks docs
VS Code Copilot
Status: Fully supported (preview)
Hook Paradigm: JSON stdin/stdout
VS Code Copilot uses the same JSON stdin/stdout paradigm as Claude Code with PascalCase hook names. It also provides unique hooks for subagent lifecycle.
Hook Names:
PreToolUse-- fires before a tool is executedPostToolUse-- fires after a tool completesPreCompact-- fires before context compactionSessionStart-- fires when a session startsStop-- fires when agent stops (unique to VS Code)SubagentStart-- fires when a subagent starts (unique to VS Code)SubagentStop-- fires when a subagent stops (unique to VS Code)
Blocking: permissionDecision: "deny" (same as Claude Code)
Arg Modification: updatedInput inside hookSpecificOutput wrapper (NOT flat like Claude Code)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": { ... }
}
}Output Modification: additionalContext inside hookSpecificOutput, or decision: "block" + reason
MCP Tool Naming: Uses f1e_ prefix (not mcp__server__tool)
Session ID: sessionId (camelCase, not session_id)
Configuration:
- Primary:
.github/hooks/*.json - Also reads:
.claude/settings.json - MCP config:
.vscode/mcp.json
Environment Detection:
VSCODE_PIDenvironment variableTERM_PROGRAM=vscode
Hook Commands:
context-mode hook vscode-copilot pretooluse
context-mode hook vscode-copilot posttooluse
context-mode hook vscode-copilot precompact
context-mode hook vscode-copilot sessionstartKnown Issues / Caveats:
- Preview status -- API may change without notice
- Matchers are parsed but IGNORED (all hooks fire on all tools)
- Tool input property names use camelCase (
filePathnotfile_path) - Response must be wrapped in
hookSpecificOutputwithhookEventName
JetBrains Copilot
Status: Fully supported (preview)
Hook Paradigm: JSON stdin/stdout
JetBrains Copilot (GitHub Copilot plugin for JetBrains IDEs) uses the same JSON stdin/stdout paradigm and hook wire protocol as VS Code Copilot. It shares hook names, response format, and MCP tool naming conventions.
Hook Names:
PreToolUse-- fires before a tool is executedPostToolUse-- fires after a tool completesPreCompact-- fires before context compactionSessionStart-- fires when a session startsStop-- fires when agent stopsSubagentStart-- fires when a subagent startsSubagentStop-- fires when a subagent stops
Blocking: permissionDecision: "deny" (same as VS Code Copilot)
Arg Modification: updatedInput inside hookSpecificOutput wrapper (same as VS Code Copilot)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": { ... }
}
}Output Modification: additionalContext inside hookSpecificOutput, or decision: "block" + reason
MCP Tool Naming: Uses f1e_ prefix (same as VS Code Copilot)
Session ID: sessionId (camelCase)
Configuration:
- Hook config:
.github/hooks/*.json - MCP config: Settings UI (Settings > Tools > AI Assistant > MCP)
Hook Commands:
context-mode hook jetbrains-copilot pretooluse
context-mode hook jetbrains-copilot posttooluse
context-mode hook jetbrains-copilot precompact
context-mode hook jetbrains-copilot sessionstartKnown Issues / Caveats:
- Preview status -- API may change without notice
- Shares the same hook wire protocol as VS Code Copilot
- MCP servers are configured via Settings UI, not a file
- Requires GitHub Copilot plugin v1.5.57+
GitHub Copilot CLI
Status: Supported (native hooks + MCP)
Hook Paradigm: JSON stdin/stdout
The standalone GitHub Copilot CLI (copilot) is user-home rooted under ~/.copilot (override with COPILOT_HOME). Its hook config uses camelCase event keys, and its command output contract is top-level (permissionDecision, modifiedArgs, additionalContext) rather than the VS Code hookSpecificOutput wrapper โ context-mode's copilot-cli adapter formats responses accordingly.
Hook config keys: (Copilot CLI 1.0.59 fires six events context-mode uses; verified against the @github/copilot binary)
preToolUse-- fires before a tool is executedpostToolUse-- fires after a tool completespreCompact-- fires before context compactionsessionStart-- fires when a session startsuserPromptSubmitted-- fires when the user submits a prompt (user-prompt capture)agentStop-- fires when the agent stops (session-end capture)
Hook config shape: flat { "type": "command", "command": "..." } entries (NOT the Claude-Code nested { matcher, hooks: [...] } shape). context-mode also writes a top-level "version": 1, but this field is optional โ the Copilot CLI accepts hook config files that omit it (copilot-cli changelog.md:1109). We pin it for self-documentation, not because the runtime requires it.
Blocking: top-level permissionDecision: "deny" + permissionDecisionReason
Arg Modification: top-level modifiedArgs
Output Modification: not supported (the posttooluse hook is capture-only)
Context Injection: top-level additionalContext โ SessionStart is the confirmed channel (verified reaching the model). PreToolUse/PostToolUse additionalContext is best-effort/unverified; context-mode's posttooluse hook is capture-only and emits no context.
Configuration:
- Plugin (recommended): context-mode ships a Copilot CLI plugin bundle at
configs/copilot-cli/โ a root.mcp.json(MCP),hooks.json(the six capture hooks), a routing skill (skills/context-mode/), and a.github/plugin/plugin.jsonmanifest.copilot plugin install mksglu/context-mode:configs/copilot-cliregisters all of it in one command (no clone, nocontext-mode upgrade/agent call). The bundle's.mcp.jsonpinsCONTEXT_MODE_PLATFORM=copilot-cli, so the server self-identifies as Copilot andctx_upgrade/detection resolvecopilot-clieven when Claude Code is co-installed (whose~/.claude/would otherwise win). Verified on Windows viacopilot --plugin-dir:ctx_executeresolves and thePostToolUsehook captures non-MCP tool I/O into the session DB. (This.mcp.jsonis the one committed instance in the repo โ.gitignoreun-ignores exactly this path, since a Copilot plugin has no other way to declare MCP.) - MCP (manual, no plugin): register with Copilot CLI's own command โ
copilot mcp add context-mode -- context-modeโ which writes~/.copilot/mcp-config.json(or$COPILOT_HOME/mcp-config.json). (Alsocopilot mcp list/copilot mcp remove.) - Hook config (manual, no plugin):
$COPILOT_HOME/hooks/context-mode.jsonor~/.copilot/hooks/context-mode.json(written bycontext-mode upgrade; standalone hooks fire โ verified โ independent of any plugin) - Instruction files:
.github/copilot-instructions.md,AGENTS.md
Detection:
- MCP protocol handshake (
clientInfo.name: "GitHub Copilot CLI"/"copilot-cli") - Config-dir marker: a context-mode-written file under
~/.copilot/(or$COPILOT_HOMEโ the marker check honors it) โmcp-config.jsonorhooks/context-mode.json, not a bare~/.copilot/directory, so a co-installed-but-unconfigured Copilot CLI is not mis-detected (probed before the generic~/.claudefallback) - Fallback:
CONTEXT_MODE_PLATFORM=copilot-clioverride
Hook Commands:
context-mode hook copilot-cli pretooluse
context-mode hook copilot-cli posttooluse
context-mode hook copilot-cli precompact
context-mode hook copilot-cli sessionstart
context-mode hook copilot-cli userpromptsubmit
context-mode hook copilot-cli stopKnown Issues / Caveats:
- The top-level
"version": 1is optional: the Copilot CLI accepts hook config files that omit the version field (copilot-cli changelog.md:1109). context-mode pins"version": 1for self-documentation; omitting it does not stop hooks from firing. - Event names can be camelCase or PascalCase โ the Copilot CLI accepts PascalCase event names alongside camelCase (copilot-cli changelog.md:1065). context-mode uses the native camelCase names; PascalCase is not silently ignored.
COPILOT_HOMErelocates the hook config, the MCP config, and the context-mode session-DB root (the adapter'sgetSessionDir()honors it, so the server reads sessions from the same place the hook runtime writes them).
Sources:
- Hooks schema: GitHub Copilot CLI hooks configuration
- Feature request: #775
Cursor
Status: Supported (native hooks, v1 scope)
Hook Paradigm: JSON stdin/stdout
Cursor uses native lower-camel hook names and flat hook entries in .cursor/hooks.json or ~/.cursor/hooks.json. context-mode treats Cursor as a first-class adapter and does not rely on Claude-compat wrappers for official support.
Hook Names:
preToolUse-- fires before a tool is executedpostToolUse-- fires after a tool completesstop-- fires when agent turn ends (can send followup_message to continue loop)afterAgentResponse-- fires after assistant response (fire-and-forget, receives full response text)sessionStart-- documented but currently rejected by Cursor's validator (forum report)
Blocking: { "permission": "deny", "user_message": "..." }
Arg Modification: not natively supported (Cursor does not have updated_input)
Output Modification: not supported in v1
Session Context Injection: { "additional_context": "..." }
Session ID Extraction Priority:
conversation_id(stdin JSON)CURSOR_TRACE_IDenvironment variable- Parent process ID fallback
Platform Detection Env Vars:
CURSOR_TRACE_ID(MCP server context)CURSOR_CLI(integrated terminal context)~/.cursor/directory fallback (medium confidence)
Configuration:
- Project:
.cursor/hooks.json - User:
~/.cursor/hooks.json - MCP config:
.cursor/mcp.jsonor~/.cursor/mcp.json - Marketplace plugin (recommended):
.cursor-plugin/plugin.jsonat the repo root auto-registers MCP, hooks, rules, and skills. Manifest explicitly pointshooksat./hooks/cursor/hooks.jsonto avoid colliding with the Claude-format./hooks/hooks.json. Local install:ln -s <repo> ~/.cursor/plugins/local/context-mode. Plugin hook commands usenpx -y context-mode hook cursor <event>so no global install is required.
Plugin/native duplication: context-mode doctor warns when both the plugin and .cursor/hooks.json register context-mode hooks (each event would otherwise fire twice). Remove one configuration to keep events single-fire.
Hook Commands:
context-mode hook cursor pretooluse
context-mode hook cursor posttooluse
context-mode hook cursor stopKnown Issues / Caveats:
preCompactis intentionally not shipped in v1stophook receives:conversation_id,status,loop_count,transcript_path; returnsfollowup_messageto continueafterAgentResponseis fire-and-forget (receivestext, no return value expected)- Hook payloads name MCP tools as
MCP:<tool>and need adapter normalization - Claude-compatible Cursor behavior exists, but native Cursor config is the supported path
additional_contextin postToolUse and sessionStart hooks is accepted but NOT surfaced to the model (Cursor upstream bug โ forum #155689, forum #156157). Routing enforcement relies on.mdcrules file and MCP tool descriptions instead.
OpenClaw
Status: Fully supported
Hook Paradigm: TS Plugin (gateway plugin via api.registerHook() / api.on())
OpenClaw is an OpenAI-stack agent gateway. context-mode ships as a native gateway plugin that registers hooks through OpenClaw's plugin API rather than the JSON stdin/stdout wire protocol. The same plugin entry also registers context-mode as a context engine, owning compaction.
Hook Names:
tool_call:before-- equivalent to PreToolUsetool_call:after-- equivalent to PostToolUsecommand:new-- equivalent to SessionStart (fires on each new gateway command)before_prompt_build-- lifecycle hook for routing instruction injectionregisterContextEngine(withownsCompaction) -- equivalent to PreCompact
Blocking: return { block: true, blockReason: "..." } from the tool_call:before handler
Arg Modification: mutate event.params in the tool_call:before handler (or return { params: ... })
Output Modification: not supported (the plugin paradigm exposes args/context, not the rendered tool output)
Context Injection: via before_prompt_build (session-level) and registerContextEngine (compaction-level)
Path Resolution:
- Detection root:
~/.openclaw/ - Plugin install:
~/.openclaw/extensions/context-mode/ - Project config:
openclaw.jsonor.openclaw/openclaw.json - Global config fallback:
~/.openclaw/openclaw.json - Project dir:
process.cwd()(the gateway provides no dedicated env var) - Memory dir: project-relative
./memory - Session dir:
~/.openclaw/context-mode/sessions/ - Routing instructions:
AGENTS.md
Configuration:
openclaw.jsonregisters context-mode underplugins.entries["context-mode"]({ "enabled": true })plugins.slots.contextEngine = "context-mode"enables ownership of compaction- No CLI hook command; OpenClaw imports the plugin module directly
Notes / Caveats:
- TS plugin paradigm โ hooks run in-process, so there is no shell command to chmod and no platform-specific stdin/stdout quirks
askdecisions are converted toblock(with the original reason) since the gateway has no interactive confirmation pathcontextdecisions insidetool_call:beforeare dropped โ context injection must be routed throughbefore_prompt_buildor the registered context engine- Session ID falls back to
pid-${process.ppid}when the gateway does not surface one
Zed
Status: MCP-only (no hooks)
Hook Paradigm: MCP-only
Zed is a code editor with first-class MCP support but no hook pipeline. context-mode runs purely through Zed's context_servers configuration; routing enforcement falls back to the AGENTS.md instruction file (~60% compliance).
Hook Support:
- PreToolUse: --
- PostToolUse: --
- PreCompact: --
- SessionStart: --
- Stop: --
- Can modify args: --
- Can modify output: --
- Can inject session context: --
The hook adapter exists only to satisfy the interface contract โ every parser throws Error("Zed does not support hooks") and every formatter returns undefined.
Path Resolution:
- Detection root:
~/.config/zed/ - Settings file:
~/.config/zed/settings.json - MCP registration:
context_serversobject insidesettings.json - Session dir:
~/.config/zed/context-mode/sessions/ - Routing instructions:
AGENTS.md(sourced fromconfigs/zed/AGENTS.mdin the package, with an inline fallback if missing)
Detection:
- Auto-detected via the presence of
~/.config/zed/ - Override via
CONTEXT_MODE_PLATFORM=zed
Notes / Caveats:
- No hook adapter implies no automatic routing โ the model must follow AGENTS.md voluntarily
- No marketplace or plugin registry for Zed;
getInstalledVersion()always reportsnot installed validateHooksalways returns a singlewarnrow reminding the user that Zed exposes only MCP integrationconfigureAllHooks,setHookPermissions, andupdatePluginRegistryare intentional no-ops
OMP (Oh My Pi)
Status: MCP-only (no hooks)
Hook Paradigm: MCP-only
Oh My Pi (OMP) is a Pi-compatible harness that stores its agent state under ~/.omp/agent/ (overridable via OMP_PROCESSING_AGENT_DIR). Before the dedicated adapter, OMP detection fell through to pi and storage rooted under another harness's directory (typically ~/.claude/), per issue #473. The OMP adapter exists primarily to keep ~/.omp/context-mode/ isolated, not to provide hook integration โ OMP, like Antigravity/Kiro/Zed, runs context-mode purely as an MCP server.
Hook Support:
- PreToolUse: --
- PostToolUse: --
- PreCompact: --
- SessionStart: --
- Stop: --
- Can modify args: --
- Can modify output: --
- Can inject session context: --
The hook adapter exists only to satisfy the interface contract โ every parser throws Error("OMP does not support hooks") and every formatter returns undefined.
Path Resolution:
- Agent root:
$OMP_PROCESSING_AGENT_DIRif set, else~/.omp/agent/ - Settings file:
<agent root>/mcp_config.json - MCP registration:
mcpServersobject insidemcp_config.json - Session dir:
~/.omp/context-mode/sessions/(intentionally rooted at~/.omp/, not the agent dir, so multiple OMP instances on one host share an index without colliding session DBs) - Routing instructions:
PI.md(Pi-compatible filename โ OMP shares the Pi instruction surface)
Detection (priority order, listed BEFORE pi so OMP is never misclassified):
OMP_PROCESSING_AGENT_DIRenv var (high confidence)~/.omp/directory presence (medium confidence)CONTEXT_MODE_PLATFORM=ompoverride
Notes / Caveats:
- No hook adapter implies no automatic routing โ the model must follow
PI.mdvoluntarily (~60% compliance) - No marketplace or plugin registry for OMP;
getInstalledVersion()reportsnot installedunless anextensions/context-mode/package.jsonexists under the agent dir validateHooksalways returns a singlewarnrow reminding the user that OMP exposes only MCP integrationconfigureAllHooks,setHookPermissions, andupdatePluginRegistryare intentional no-ops
Capability Matrix (Quick Reference)
| Capability | Claude Code | Qwen Code | Gemini CLI | VS Code Copilot | JetBrains Copilot | GitHub Copilot CLI | Cursor | OpenCode | KiloCode | OpenClaw | Codex CLI | Kimi Code | Antigravity | Antigravity CLI (agy) |
Kiro | Zed | Pi | OMP |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| PreToolUse | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes*** | Yes | -- | Bounded | Yes | -- | -- | -- |
| PostToolUse | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | Yes (capture-only) | Yes | -- | -- | -- |
| PreCompact | Yes | Yes | Yes | Yes | Yes | Yes | -- | Yes* | Yes* | Yes | Yes**** | Yes | -- | -- | -- | -- | -- | -- |
| SessionStart | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | -- | Yes | Yes | Yes | -- | -- | -- | -- | -- | -- |
| Stop | -- | -- | -- | Yes | Yes | Yes | Yes | -- | -- | -- | Yes | Yes | -- | Best-effort capture | -- | -- | -- | -- |
| Modify Args | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | Yes | -- | -- | -- | -- | -- | -- |
| Modify Output | Yes | Yes | Yes | Yes | Yes | No | No | Yes** | Yes** | No | -- | Yes | -- | -- | -- | -- | -- | -- |
| Inject Context | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | -- | Yes | Yes | Yes | -- | -- | -- | -- | -- | -- |
| Block Tools | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | -- | Bounded | Yes | -- | -- | -- |
| MCP/native tool support | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Native plugin | Native plugin | Native plugin | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
* OpenCode experimental.session.compacting is experimental
** OpenCode has a TUI rendering bug for bash tool output (#13575)
*** Codex CLI PreToolUse supports deny only (no additionalContext); context injection works via PostToolUse and SessionStart
**** Codex CLI PreCompact is runtime-gated on builds that emit the event
Hook Response Format Comparison
Blocking a Tool
| Platform | Response Format |
|---|---|
| Claude Code | { "permissionDecision": "deny", "reason": "..." } |
| Gemini CLI | { "decision": "deny", "reason": "..." } |
| VS Code Copilot | { "permissionDecision": "deny", "reason": "..." } |
| JetBrains Copilot | { "permissionDecision": "deny", "reason": "..." } |
| Cursor | { "permission": "deny", "user_message": "..." } |
| OpenCode | throw new Error("...") |
| Codex CLI | { "hookSpecificOutput": { "permissionDecision": "deny" } } or exit code 2 |
| Kimi Code | { "hookSpecificOutput": { "permissionDecision": "deny" } } or exit code 2 |
Modifying Tool Input
| Platform | Response Format |
|---|---|
| Claude Code | { "updatedInput": { ... } } |
| Gemini CLI | { "hookSpecificOutput": { "tool_input": { ... } } } |
| VS Code Copilot | { "hookSpecificOutput": { "hookEventName": "PreToolUse", "updatedInput": { ... } } } |
| JetBrains Copilot | { "hookSpecificOutput": { "hookEventName": "PreToolUse", "updatedInput": { ... } } } |
| Cursor | { "updated_input": { ... } } |
| OpenCode | { "args": { ... } } (mutation) |
| Codex CLI | N/A (updatedInput in schema but not implemented) |
| Kimi Code | { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "allow", "updatedInput": { ... } } } |
Injecting Additional Context (PostToolUse)
| Platform | Response Format |
|---|---|
| Claude Code | { "additionalContext": "..." } |
| Gemini CLI | { "hookSpecificOutput": { "additionalContext": "..." } } |
| VS Code Copilot | { "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "..." } } |
| JetBrains Copilot | { "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "..." } } |
| Cursor | { "additional_context": "..." } |
| OpenCode | { "additionalContext": "..." } |
| Codex CLI | { "hookSpecificOutput": { "additionalContext": "..." } } |
| Kimi Code | { "hookSpecificOutput": { "hookEventName": "PostToolUse", "additionalContext": "..." } } |
CLI Hook Dispatcher
All hook-based platforms use the CLI dispatcher pattern instead of direct node paths:
context-mode hook <event>The dispatcher resolves the hook script relative to the installed package and dynamically imports it. Stdin/stdout flow through naturally since it runs in the same process.
Advantages over node ./node_modules/... paths:
- Works from any directory (no per-project
npm installneeded) - Single global install serves all projects
context-mode upgradeupdates hooks in-place- Short, portable command strings in settings files
Supported dispatches:
| Platform | Events |
|---|---|
claude-code |
pretooluse, posttooluse, precompact, sessionstart, userpromptsubmit |
gemini-cli |
beforetool, aftertool, precompress, sessionstart |
vscode-copilot |
pretooluse, posttooluse, precompact, sessionstart |
jetbrains-copilot |
pretooluse, posttooluse, precompact, sessionstart |
cursor |
pretooluse, posttooluse, stop |
codex |
pretooluse, posttooluse, precompact, sessionstart, userpromptsubmit, stop |
kimi |
pretooluse, posttooluse, precompact, sessionstart, userpromptsubmit, stop |
qwen-code |
pretooluse, posttooluse, precompact, sessionstart, userpromptsubmit |
copilot-cli |
pretooluse, posttooluse, precompact, sessionstart, userpromptsubmit, stop |
antigravity-cli |
pretooluse, posttooluse, stop |
kiro |
pretooluse, posttooluse |
OpenCode, KiloCode, and OpenClaw use a TS plugin paradigm (no command dispatcher). Pi and OMP register hooks through their own host APIs rather than the CLI dispatcher; Antigravity IDE and Zed are MCP-only (no hooks).
SQLite Backend Selection
context-mode automatically selects the best SQLite backend at runtime based on the environment:
| Priority | Condition | Backend | Why |
|---|---|---|---|
| 1 | Bun runtime | bun:sqlite |
Built-in, no native addon |
| 2 | Linux + Node.js >= 22.5 | node:sqlite |
Built-in, avoids SIGSEGV from V8 madvise bug |
| 3 | All other environments | better-sqlite3 |
Mature native addon, prebuilt binaries |
Why node:sqlite on Linux? Node.js's V8 garbage collector can call madvise(MADV_DONTNEED) on memory ranges that overlap better-sqlite3's native addon .got.plt section, corrupting resolved symbol addresses and causing sporadic SIGSEGV crashes (1-4/hour on Node v22-v24). node:sqlite is compiled into the Node.js binary itself โ no separate .node file, no dlopen(), no .got.plt to corrupt.
Fallback: If node:sqlite is unavailable (Node < 22.5), context-mode silently falls back to better-sqlite3. No user configuration needed.
Override: Not currently supported โ backend selection is automatic. If you need to force a specific backend, open an issue.
Utility Commands
All platforms support utility commands via MCP meta-tools:
| Command | What it does |
|---|---|
ctx stats |
Show context savings, call counts, and session statistics |
ctx doctor |
Diagnose installation: runtimes, hooks, FTS5, versions |
ctx upgrade |
Update from GitHub, rebuild, reconfigure hooks |
ctx purge |
Permanently deletes all indexed content from the knowledge base |
How they work: The MCP server exposes stats, doctor, upgrade, and purge tools. The <ctx_commands> section in routing instructions (CLAUDE.md, GEMINI.md, AGENTS.md, copilot-instructions.md) maps natural language triggers to MCP tool calls. The doctor and upgrade tools return shell commands that the LLM executes and formats as a checklist. The purge tool permanently deletes all indexed content from the knowledge base and is the sole reset mechanism.
File: docs/UPSTREAM-CREDITS.md
Upstream Skill Credits
context-mode references a small set of operating-discipline skills
authored by Matt Pocock (MIT). They are the operational backbone of thecontext-mode-ops
skill (/diagnose, /tdd, /grill-me, /grill-with-docs,/improve-codebase-architecture).
These skills are referenced, not vendored. Maintainers install them
locally via the upstream skills CLI. Shipping them to every plugin
install would pay description tokens for tools end users never invoke,
so they stay out of this repo.
Source
- Repository: https://github.com/mattpocock/skills
- License: MIT (Copyright (c) 2026 Matt Pocock)
- Author: Matt Pocock
Referenced (not vendored)
Install the upstream skills into your local Claude Code:
npx skills add mattpocock/skills \
-s tdd grill-me grill-with-docs diagnose improve-codebase-architecture \
-a claude-code -y| Skill | Upstream path |
|---|---|
/diagnose |
skills/engineering/diagnose/ |
/tdd |
skills/engineering/tdd/ |
/grill-me |
skills/productivity/grill-me/ |
/grill-with-docs |
skills/engineering/grill-with-docs/ |
/improve-codebase-architecture |
skills/engineering/improve-codebase-architecture/ |
Why reference instead of vendor?
The owner operating directive at the top ofcontext-mode-ops/SKILL.md
treats these skills as mandatory tools, not advisory references.
Maintainers running /context-mode-ops install the upstream skills
once via the command above; end users do not need them. This keeps
the plugin install lean while preserving the discipline.
License preservation
The MIT license terms travel with the source. Full license text lives
at the upstream repository
(https://github.com/mattpocock/skills). No portion is relicensed.
Skills remain MIT under Matt Pocock's copyright.