# Hook up Claude Code and Codex (https://condensate.dev/guides/connect-agents/)

> Install the Condensate plugin in Claude Code and Codex, then check it.

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 use `http://127.0.0.1:24787` here and everywhere this page says 4787. [Connect tools and hooks](https://condensate.dev/guides/middleware/) shows how to start it.
- **The middleware checks a key.** The key is the value of `CONDENSATE_LOCAL_API_KEY` that the middleware was started with. You make it yourself, as described in the next section. `/healthz` answers without a key, so it does not prove the key works. The key check is in [Get the key](#get-the-key).
- **The middleware has at least one upstream.** `/mcp` answers HTTP 404 until `CONDENSATE_MCP_UPSTREAMS` lists at least one server. Hooks work without one.
- **Bun is installed.** Run `bun --version`. The repository pins Bun 1.3.13 in `package.json`, and the installer refuses any other version.
- **You have a local checkout of the repository**, with dependencies installed by `bun install --frozen-lockfile` from its root. The plugin installs from this checkout, or straight from GitHub. See [Known limits](#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`.

1. Make the key and save it in the file the runtime reads for steering, readable only by you:

   ```sh
   mkdir -p ~/.config/condensate/middleware
   (umask 077 && openssl rand -hex 32 > ~/.config/condensate/middleware/api-key)
   chmod 600 ~/.config/condensate/middleware/api-key
   ```

2. Start the middleware with that key. In the developer setup, run this from the repository root:

   ```sh
   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.ts
   ```

   Replace the upstream with a server you run.
   To see the code-mode tools, also set `CONDENSATE_CODE_MODE=opt-in` and `CONDENSATE_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.env` instead.
   Set `CONDENSATE_LOCAL_API_KEY=<your-middleware-key>` and `CONDENSATE_MCP_UPSTREAMS=<your-upstream-json>` as plain `KEY=value` lines, without quotes or `export`.
   The installer leaves both empty.
   Stop the installer with Ctrl-C and run `./scripts/install.sh` again.

3. Check the key against the gateway:

   ```sh
   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 includes `harness__set_mode` and 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

1. Build the plugin's generated files from the repository root:

   ```sh
   bun packages/condensate-plugin/generate.ts
   ```

   It prints `Generated 0 files.` when every generated file, the skills included, is current. Git tracks those files, so a fresh checkout is already current.

2. 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:

   ```sh
   export CONDENSATE_KEY="$(cat ~/.config/condensate/middleware/api-key)"
   export CONDENSATE_URL=http://127.0.0.1:4787
   ```

3. Start `claude` from that shell, and add the checkout as a marketplace. Use the absolute path of the repository root:

   ```text
   /plugin marketplace add /absolute/path/to/condensate
   ```

4. Install the plugin:

   ```text
   /plugin install condensate@condensate
   ```

5. Exit, and start a new `claude` session from the same shell, so the hooks and the MCP server load with `CONDENSATE_KEY` set.

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

1. Run `curl -sS http://127.0.0.1:4787/healthz` and note `sessions`.
2. In the new Claude Code session, send any prompt.
3. Run the `curl` again. `sessions` is 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",...}`.
4. Run `/mcp` in the session. The Condensate server is listed as connected, and its tools are the ones the `tools/list` check 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](#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`.

1. Build the generated files, as for Claude Code:

   ```sh
   bun packages/condensate-plugin/generate.ts
   ```

2. Add the checkout as a marketplace:

   ```sh
   codex plugin marketplace add /absolute/path/to/condensate
   ```

3. Set the key, and the address if it is not the default, in the shell that starts Codex:

   ```sh
   export CONDENSATE_KEY="$(cat ~/.config/condensate/middleware/api-key)"
   export CONDENSATE_URL=http://127.0.0.1:4787
   ```

4. Run `codex`, then `/plugins`. Select the Condensate marketplace and install the Condensate plugin. `codex plugin add condensate@condensate` installs it without the menu.
5. Review the plugin's hooks and trust them. Codex does not run a plugin's hooks until you do.
6. Exit, and start a new `codex` session 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

1. Note `sessions` from `curl -sS http://127.0.0.1:4787/healthz`.
2. Send any prompt in the new Codex session.
3. Run the `curl` again. `sessions` is one higher, and the middleware's output shows `auth_ok` and `verdict` lines 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.
4. Run `/mcp` in 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.

1. Set `REPO` to the absolute path of the repository checkout, and `URL` to `http://127.0.0.1:4787`, or `http://127.0.0.1:24787` for an installer setup.
2. Run `curl -sS "$URL/healthz"`. Expect output starting `{"ok":true`.
3. 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.
4. Run the `tools/list` request with the key in `x-api-key`. Expect `"result":{"tools":[`. On 401, 404 or 503, stop and report the status.
5. Run `bun packages/condensate-plugin/generate.ts` in `REPO`. Expect exit code 0 and a line starting `Generated`.
6. Pipe a test event into the hook script. Expect exit code 0 and no output. Then run `curl -sS "$URL/healthz"` again and note `sessions`, which now counts the test event.
7. Add the marketplace and install the plugin with the host's CLI.
8. For Codex, stop and ask a person to review and trust the plugin's hooks in `codex` before you continue.
9. Tell the person to start a new session with `CONDENSATE_KEY` and `CONDENSATE_URL` set. Then run `curl -sS "$URL/healthz"` and expect `sessions` to be higher than in step 6.

```sh
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/condensate` is private, so each host clones it with your git credentials. Claude Code clones `owner/repo` over SSH unless `CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1` is set.
- **The plugin stores no key.** Each session reads `CONDENSATE_KEY` from 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-docs` and `transitions-dev` are not bundled, because each needs a large set of reference files. `install-anti-slop` is 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](https://condensate.dev/guides/middleware/) covers the gateway, hook verdicts, consent tiers and code mode.
- [Connect](https://condensate.dev/connect/) explains what the middleware decides and why.
- `apps/middleware/docs/hook-contract.md` in the repository describes the envelope the hook sends.
