# Claude Code & Codex integration
AgStatus watches your agents through their hook systems — Claude Code's
[hooks](https://code.claude.com/docs/en/hooks) and OpenAI Codex's
[lifecycle hooks](https://developers.openai.com/codex/config-reference) use
the same shape (event JSON on stdin with `hook_event_name`, `session_id`,
`cwd`), so one small script serves both: it receives each event and posts a
status webhook to your board. Two ways to set it up:
1. **`npx agstatus init`** (recommended) — installs a dependency-free Node
hook and wires up Claude Code, plus Codex when `~/.codex` exists.
2. **Manual bash hook** — the original `hooks/claude-status-hook.sh`, for
people who want to see and customize every moving part (Claude Code only).
Both hooks are deliberately non-blocking: any failure (server down, bad
secret, malformed payload) is swallowed and the agent continues normally.
The Node hook additionally enforces a 3 s HTTP timeout, a ~4 s overall
safety timeout, and never writes to stdout (Codex interprets hook stdout as
behavior-control decisions).
## `npx agstatus init`
```bash
npx agstatus init
```
> Until the package is published to npm, run it from a clone of this repo:
>
> ```bash
> npm --prefix cli install && npm --prefix cli run build && node cli/dist/cli.js init
> ```
What it does, in order:
1. Contacts the server (`GET /api/config`) — the hosted instance by default.
2. Acquires a board: claims your pairing code (`--code`), creates a new
private board (multi-tenant servers), or targets the server's single board
(legacy servers).
3. Copies the hook to `~/.claude/hooks/agstatus-hook.js` (plain Node,
no dependencies).
4. Merges the hook registrations and env vars into `~/.claude/settings.json`,
writing a backup to `settings.json.agstatus-backup` first. Your existing
settings and hooks are preserved; re-running `init` replaces only the
AgStatus entries.
5. Prints your dashboard URL and a QR code to open it on your phone.
### Flags
| Flag | Purpose |
| ------------------ | ------- |
| `--url ` | Server to use (default: `https://claude-status.kardan.ddns.net`; the `AGSTATUS_URL` env var also overrides it, with `--url` winning). |
| `--code XXXX-XXXX` | Pair with a board created elsewhere (e.g. the iOS app) instead of creating a new one. Case and dashes don't matter. |
| `--secret ` | Webhook secret for self-hosted single-tenant servers that set `WEBHOOK_SECRET` (stored as `CLAUDE_STATUS_SECRET`). |
| `--minimal` | Privacy mode: send tool names only, never command text (sets `AGSTATUS_DETAIL=off`; re-running without the flag removes it). |
| `--codex` | Also set up OpenAI Codex even when `~/.codex` isn't detected (creates it). |
| `--no-codex` | Skip Codex setup. Default: auto-configure when `~/.codex` exists. |
| `--no-qr` | Skip the QR code (narrow terminals, scripts). |
### Pairing codes
Boards created elsewhere can hand you a short-lived code instead of a URL:
the board owner taps "Pair your computer" in the iOS app (or calls
`POST /w//pair`) and gets a single-use code valid for 15 minutes
(max 3 outstanding per board). `agstatus init --code XXXX-XXXX` exchanges it
via `POST /api/pair/claim` for the board's URLs. Claims are rate limited to
10/min/IP. Details in [docs/api.md](api.md#pairing-endpoints).
### Other commands
```bash
npx agstatus status # show configured URL, hook file, server reachability + session count
npx agstatus uninstall # remove the hook file and AgStatus settings entries (backup kept)
npx agstatus help # usage
```
`uninstall` removes only AgStatus's hook registrations and the
`CLAUDE_STATUS_URL` / `CLAUDE_STATUS_SECRET` / `AGSTATUS_DETAIL` env keys —
everything else in `settings.json` is left untouched.
## How events map to statuses
The Node hook applies this mapping. The bash hook covers the same Claude Code
events but predates Codex support, so it has no `PermissionRequest` or
`apply_patch` handling (the last two rows below are Codex-only):
| Hook event | Board status | Notes |
| -------------------------------------------------------------- | -------------- | ----- |
| `SessionStart` | `idle` | New session appears as soon as Claude starts. |
| `PreToolUse` — `Edit` / `Write` / `MultiEdit` / `NotebookEdit` | `coding` | Card shows the tool name. |
| `PreToolUse` — `Bash` (test runner) | `testing` | Detected via `pytest`/`jest`/`vitest`/`go test`/`cargo test`/etc. |
| `PreToolUse` — `Bash` (other) | `coding` | Card shows the command, truncated to 120 chars (tool name only with `--minimal`). |
| `PreToolUse` — `Task` / `WebSearch` / `WebFetch` | `planning` | Investigation tools. |
| `Notification` | `blocked` | Claude Code: permission prompts and other attention-needed events — this is what triggers a push. |
| `PermissionRequest` | `blocked` | Codex: fires before approval prompts — same push trigger. |
| `PreToolUse` — `apply_patch` | `coding` | Codex's file-edit tool; the card shows "Editing files". |
| `Stop` | `idle` | Turn finished, waiting for the next prompt. |
| `SessionEnd` | _card removed_ | Claude Code only — the hook calls `DELETE /sessions/:id` so the card disappears. Codex has no session-end hook; its cards expire via the server's session TTL instead. |
Other tools and events are ignored. The card `name` and `project` default to
the basename of the session's working directory, so multiple sessions are
easy to tell apart.
## OpenAI Codex specifics
`agstatus init` configures Codex automatically when `~/.codex` exists
(`CODEX_HOME` is respected; `--no-codex` opts out, `--codex` forces it):
1. The same hook script is copied to `~/.codex/hooks/agstatus-hook.js`.
2. Hook registrations are merged into `~/.codex/hooks.json` (backup written
alongside; existing hooks are preserved; re-running replaces only the
AgStatus entries). Codex has no settings `env` block, so the board URL is
embedded in the registered command string — you can read or change it
right in `hooks.json`.
3. **One-time step:** Codex requires you to trust new hooks. Run `/hooks`
inside Codex and approve the AgStatus entries — until then they won't fire.
Events wired: `SessionStart`, `PreToolUse` (matcher
`^(Bash|apply_patch|Edit|Write)$`), `PermissionRequest`, and `Stop`, each with
a 10 s timeout (the script itself exits within ~4 s).
### Environment variables read by the hooks
| Variable | Purpose |
| ---------------------- | ------- |
| `CLAUDE_STATUS_URL` | Required. Base URL the hook posts to — a board URL (`https:///w/`) or a legacy server origin. The hook appends `/webhook` and `/sessions/` itself, and tolerates a trailing `/` or `/webhook`. |
| `CLAUDE_STATUS_SECRET` | Optional. Sent as `X-Webhook-Secret` (legacy servers with `WEBHOOK_SECRET` set). Workspace boards don't need it — the token in the URL is the auth. |
| `AGSTATUS_DETAIL=off` | Node hook only. Send tool names instead of command text (what `--minimal` sets). |
## Manual setup: the bash hook
The original hook at [`hooks/claude-status-hook.sh`](../hooks/claude-status-hook.sh)
does the same job with `curl` + `jq`, and is the easiest one to customize.
### 1. Prerequisites
- A reachable AgStatus server (see [docs/self-hosting.md](self-hosting.md)).
- `curl` and `jq` installed (`brew install jq` on macOS).
### 2. Install the script
```bash
mkdir -p ~/.claude/hooks
cp hooks/claude-status-hook.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/claude-status-hook.sh
```
### 3. Configure `~/.claude/settings.json`
For all sessions (user scope), edit `~/.claude/settings.json`. For one
project, use `.claude/settings.json` in that repo instead.
```json
{
"env": {
"CLAUDE_STATUS_URL": "https://status.example.com",
"CLAUDE_STATUS_SECRET": "your-shared-secret"
},
"hooks": {
"SessionStart": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/claude-status-hook.sh" }
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit|NotebookEdit|Bash|Task|WebSearch|WebFetch",
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/claude-status-hook.sh" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/claude-status-hook.sh" }
]
}
],
"Notification": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/claude-status-hook.sh" }
]
}
],
"SessionEnd": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.claude/hooks/claude-status-hook.sh" }
]
}
]
}
}
```
Replace `CLAUDE_STATUS_URL` with your server or board URL. Drop
`CLAUDE_STATUS_SECRET` if you didn't set `WEBHOOK_SECRET` on the server.
**Using a multi-tenant instance?** Point `CLAUDE_STATUS_URL` at your board:
```json
"CLAUDE_STATUS_URL": "https:///w/"
```
No script changes needed, and no secret — the token in the URL is the auth.
## Troubleshooting
Start a fresh Claude Code session in any project. The board should show a new
card transitioning through `idle → coding → idle` as you work. If it doesn't:
```bash
# Check the CLI-installed setup end to end:
npx agstatus status
# Run the Node hook manually with a fake payload (put your board URL first):
export CLAUDE_STATUS_URL='https://claude-status.kardan.ddns.net/w/ags_yourtoken'
echo '{"hook_event_name":"SessionStart","session_id":"manual-test","cwd":"'"$PWD"'"}' \
| node ~/.claude/hooks/agstatus-hook.js
# Or the bash hook:
echo '{"hook_event_name":"SessionStart","session_id":"manual-test","cwd":"'"$PWD"'"}' \
| ~/.claude/hooks/claude-status-hook.sh
# Tail Claude Code's logs (path may vary by OS):
tail -f ~/.claude/logs/*.log
```
A `manual-test` card appearing on the board means the hook and server are
fine and the problem is in `settings.json` — check that the `hooks` entries
survived any hand-edits, and remember Claude Code reads settings at session
start, so restart the session after changes.