Once the Condensate plugin is installed, every Claude Code or Codex session sends its hook events to the middleware, which can block a prompt or tool call, add a steering note, or record a verdict. The same session also reaches the middleware's MCP gateway, so it sees your upstream tools, the code-mode tools and the plugin's skills.
Before you start
- The middleware is running. Run
curl -sS http://127.0.0.1:4787/healthz. The answer starts with{"ok":true,"sessions":. If you used the installer (./scripts/install.sh), the middleware is on port 24787, so usehttp://127.0.0.1:24787here and everywhere this page says 4787. Connect tools and hooks shows how to start it. - The middleware checks a key. The key is the value of
CONDENSATE_LOCAL_API_KEYthat the middleware was started with. You make it yourself, as described in the next section./healthzanswers without a key, so it does not prove the key works. The key check is in Get the key. - The middleware has at least one upstream.
/mcpanswers HTTP 404 untilCONDENSATE_MCP_UPSTREAMSlists at least one server. Hooks work without one. - Bun is installed. Run
bun --version. The repository pins Bun 1.3.13 inpackage.json, and the installer refuses any other version. - You have a local checkout of the repository, with dependencies installed by
bun install --frozen-lockfilefrom its root. The plugin installs from this checkout, or straight from GitHub. See Known limits.
Get the key
Condensate does not mint the key for you.
You make one random value and give it to both sides: the middleware checks it as CONDENSATE_LOCAL_API_KEY, and each agent sends it as CONDENSATE_KEY.
-
Make the key and save it in the file the runtime reads for steering, readable only by you:
mkdir -p ~/.config/condensate/middleware (umask 077 && openssl rand -hex 32 > ~/.config/condensate/middleware/api-key) chmod 600 ~/.config/condensate/middleware/api-key -
Start the middleware with that key. In the developer setup, run this from the repository root:
export CONDENSATE_HOST=127.0.0.1 export CONDENSATE_LOCAL_PRINCIPAL_ID=local-dev export CONDENSATE_LOCAL_API_KEY="$(cat ~/.config/condensate/middleware/api-key)" export CONDENSATE_MCP_UPSTREAMS='[{"name":"tools","url":"http://127.0.0.1:9000/mcp","auth":"none"}]' bun apps/middleware/backend/index.tsReplace the upstream with a server you run. To see the code-mode tools, also set
CONDENSATE_CODE_MODE=opt-inandCONDENSATE_CODE_MODE_PRINCIPALS=local-dev. The boot log has a line with"msg":"local_key_auth_enabled"when the key is in force.With the installer, edit
~/.local/state/condensate-fresh/middleware.envinstead. SetCONDENSATE_LOCAL_API_KEY=<your-middleware-key>andCONDENSATE_MCP_UPSTREAMS=<your-upstream-json>as plainKEY=valuelines, without quotes orexport. The installer leaves both empty. Stop the installer with Ctrl-C and run./scripts/install.shagain. -
Check the key against the gateway:
curl -sS http://127.0.0.1:4787/mcp \ -H "x-api-key: $(cat ~/.config/condensate/middleware/api-key)" \ -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'A working key returns JSON with
result.tools, which includesharness__set_modeand your upstream's tools named<upstream>__<tool>. HTTP 401 means the key does not match. HTTP 404 means no upstream is configured. HTTP 503 means the middleware has no key set.
The plugin stores no key.
Each agent reads CONDENSATE_KEY from the environment of the process that starts it.
Claude Code
-
Build the plugin's generated files from the repository root:
bun packages/condensate-plugin/generate.tsIt prints
Generated 0 files.when every generated file, the skills included, is current. Git tracks those files, so a fresh checkout is already current. -
Set the key, and the address if it is not the default
http://127.0.0.1:4787, in the shell that starts Claude Code:export CONDENSATE_KEY="$(cat ~/.config/condensate/middleware/api-key)" export CONDENSATE_URL=http://127.0.0.1:4787 -
Start
claudefrom that shell, and add the checkout as a marketplace. Use the absolute path of the repository root:/plugin marketplace add /absolute/path/to/condensate -
Install the plugin:
/plugin install condensate@condensate -
Exit, and start a new
claudesession from the same shell, so the hooks and the MCP server load withCONDENSATE_KEYset.
The same steps work outside a session as claude plugin marketplace add /absolute/path/to/condensate and claude plugin install condensate@condensate.
To install from GitHub instead of a checkout, skip step 1, and in step 3 add the marketplace as condensate-dev/condensate.
Check it works
- Run
curl -sS http://127.0.0.1:4787/healthzand notesessions. - In the new Claude Code session, send any prompt.
- Run the
curlagain.sessionsis one higher. In the developer setup, the middleware's output also shows two lines for the prompt:{"msg":"auth_ok","plane":"local",...}and{"msg":"verdict",...,"harness":"claude-code",...}. - Run
/mcpin the session. The Condensate server is listed as connected, and its tools are the ones thetools/listcheck returned.
If the prompt is denied with a reason that starts condensate-hook:, the hook ran but could not get a verdict.
The reason names the fault.
See If it does not work.
Codex
These steps follow Codex 0.160. Codex loads the plugin's hooks from packages/condensate-plugin/.codex-plugin/plugin.json, and starts a small stdio bridge, mcp-bridge.bundle, that forwards MCP requests to ${CONDENSATE_URL}/mcp with the key in x-api-key.
-
Build the generated files, as for Claude Code:
bun packages/condensate-plugin/generate.ts -
Add the checkout as a marketplace:
codex plugin marketplace add /absolute/path/to/condensate -
Set the key, and the address if it is not the default, in the shell that starts Codex:
export CONDENSATE_KEY="$(cat ~/.config/condensate/middleware/api-key)" export CONDENSATE_URL=http://127.0.0.1:4787 -
Run
codex, then/plugins. Select the Condensate marketplace and install the Condensate plugin.codex plugin add condensate@condensateinstalls it without the menu. -
Review the plugin's hooks and trust them. Codex does not run a plugin's hooks until you do.
-
Exit, and start a new
codexsession from the same shell.
To install from GitHub instead of a checkout, skip step 1, and in step 2 run codex plugin marketplace add condensate-dev/condensate.
Codex passes CONDENSATE_KEY and CONDENSATE_URL from its own environment to the bridge.
A key set anywhere else does not reach it.
Check it works
- Note
sessionsfromcurl -sS http://127.0.0.1:4787/healthz. - Send any prompt in the new Codex session.
- Run the
curlagain.sessionsis one higher, and the middleware's output showsauth_okandverdictlines as for Claude Code. The verdict line says"harness":"claude-code"under Codex too, because both hosts run the same hook script, which uses that event format. - Run
/mcpin the session. The Condensate server and its tools are listed.
For agents
Follow these steps in order. Stop and report the step number and the output if any expected result does not appear.
- Set
REPOto the absolute path of the repository checkout, andURLtohttp://127.0.0.1:4787, orhttp://127.0.0.1:24787for an installer setup. - Run
curl -sS "$URL/healthz". Expect output starting{"ok":true. - Read the key from
~/.config/condensate/middleware/api-key. If the file does not exist, stop and ask a person for the middleware key. Do not make a new one, because the middleware would not accept it. - Run the
tools/listrequest with the key inx-api-key. Expect"result":{"tools":[. On 401, 404 or 503, stop and report the status. - Run
bun packages/condensate-plugin/generate.tsinREPO. Expect exit code 0 and a line startingGenerated. - Pipe a test event into the hook script. Expect exit code 0 and no output. Then run
curl -sS "$URL/healthz"again and notesessions, which now counts the test event. - Add the marketplace and install the plugin with the host's CLI.
- For Codex, stop and ask a person to review and trust the plugin's hooks in
codexbefore you continue. - Tell the person to start a new session with
CONDENSATE_KEYandCONDENSATE_URLset. Then runcurl -sS "$URL/healthz"and expectsessionsto be higher than in step 6.
REPO=/absolute/path/to/condensate
URL=http://127.0.0.1:4787
cd "$REPO"
curl -sS "$URL/healthz" # {"ok":true,"sessions":N,...}
export CONDENSATE_KEY="$(cat ~/.config/condensate/middleware/api-key)"
export CONDENSATE_URL="$URL"
curl -sS "$URL/mcp" -H "x-api-key: $CONDENSATE_KEY" -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' # "result":{"tools":[...
bun packages/condensate-plugin/generate.ts # Generated N files.
echo '{"hook_event_name":"UserPromptSubmit","session_id":"connect-check","prompt":"hello"}' \
| bun packages/condensate-plugin/hooks/condensate-hook.bundle; echo "exit=$?" # exit=0, nothing else
curl -sS "$URL/healthz" # note sessions; the test event counts as one
# Claude Code
claude plugin marketplace add "$REPO"
claude plugin install condensate@condensate
# Codex: after this, a person trusts the hooks in codex
codex plugin marketplace add "$REPO"
codex plugin add condensate@condensate
curl -sS "$URL/healthz" # after a new session: sessions is higher
If it does not work
| Symptom | Cause | Fix |
|---|---|---|
Every prompt and tool call is denied with condensate middleware unreachable | The hook cannot reach the middleware. It fails closed: UserPromptSubmit and PreToolUse deny, and other events continue with one warning. | Start the middleware, or set CONDENSATE_URL to its address (port 24787 for the installer) and start a new session. CONDENSATE_HOOK_FAIL_OPEN=1 lets every fault continue, for local development only, because it removes the policy check. |
Denied with CONDENSATE_KEY is not set | The host was started from a shell without the key. | Export CONDENSATE_KEY in the shell that starts claude or codex, then start a new session. |
Denied with condensate middleware returned HTTP 401 | The key does not match the middleware's CONDENSATE_LOCAL_API_KEY. | Use the same value on both sides, and check it with the tools/list request. |
Denied with condensate middleware returned HTTP 503 | The middleware has no key or other credential setup. This is the installer's default. | Set CONDENSATE_LOCAL_API_KEY and restart the middleware. |
The Condensate MCP server fails, or Codex logs condensate-mcp: middleware returned HTTP 404 | No upstream is configured, so the middleware does not serve /mcp. | Set CONDENSATE_MCP_UPSTREAMS and restart the middleware. |
No codemode__ tools | Code mode is off, or your principal is not listed. | Set CONDENSATE_CODE_MODE=opt-in and add your principal id to CONDENSATE_CODE_MODE_PRINCIPALS. |
A change to the plugin does not show up after generate.ts | Each host keeps its own installed copy, and regenerating does not refresh it. | Uninstall the plugin in each host (claude plugin uninstall condensate@condensate, codex plugin remove condensate@condensate), install it again, and start a new session. |
| Skills are missing | The marketplace copy predates the commit that started tracking the generated skills. | Refresh the marketplace (claude plugin marketplace update condensate, codex plugin marketplace upgrade condensate), then uninstall and install the plugin again and start a new session. |
Known limits
- Install from GitHub needs read access to the repository.
condensate-dev/condensateis private, so each host clones it with your git credentials. Claude Code clonesowner/repoover SSH unlessCLAUDE_CODE_PLUGIN_PREFER_HTTPS=1is set. - The plugin stores no key. Each session reads
CONDENSATE_KEYfrom its host's environment. Rotating the key means changing it on the middleware and in that environment, then starting new sessions. - Three skills are left out.
hindsight-docsandtransitions-devare not bundled, because each needs a large set of reference files.install-anti-slopis not bundled, because it copies the anti-slop plugin from a repository checkout. - The hook script needs Bun on the host. Both hosts run it as
bun hooks/condensate-hook.bundle.
Read next
- Connect tools and hooks covers the gateway, hook verdicts, consent tiers and code mode.
- Connect explains what the middleware decides and why.
apps/middleware/docs/hook-contract.mdin the repository describes the envelope the hook sends.