Hook System
The hook system enables agents to report lifecycle events back to the command center. Claude Code hooks, OpenClaw callbacks, and custom scripts POST structured events that update the session state machine and broadcast changes over WebSocket in real time.
Endpoint
POST /api/hooks/ingest
Content-Type: application/jsonThis endpoint is unauthenticated but restricted to localhost. Hook events should only come from processes running on the same host as the OptimalOS server (e.g., Claude Code hook scripts curling localhost:3000). External requests through the Cloudflare Tunnel do not reach this endpoint.
In fabric mode, the cockpit on Hetzner mounts the same /api/hooks/ingest (route registered at src/server.ts:115, handler at src/orchestration/hooks.ts) — the endpoint, payload contract, and event map are unchanged. Sessions running on paired devices report status to the cockpit through the /ws/device multiplex and device-router.ts, not via this hook endpoint; /api/hooks/ingest remains the ingest path for locally-launched Claude Code, OpenClaw, and shell agents on whichever host runs the OptimalOS server.
Payload Format
{
"tmux_session": "agent-claude-code-abc123",
"event": "tool_use",
"metadata": "tool_use",
"agent_type": "claude-code"
}| Field | Type | Required | Description |
|---|---|---|---|
tmux_session | string | Yes* | The tmux session this event belongs to |
session_id | string | Yes* | Direct session ID (alternative to tmux_session) |
event | string | Yes | Event type (see table below) |
metadata | string | No | Free-form description of the event |
agent_type | string | No | claude-code, openclaw, or shell (defaults to claude-code) |
*At least one of tmux_session or session_id must be provided. The endpoint looks up sessions by session_id first, then falls back to tmux_session.
Event Types
Events are mapped to state machine transitions via HOOK_EVENT_MAP:
| Event | Maps to | Effect |
|---|---|---|
start | start | idle -> running |
running | hook.to_running | awaiting_review -> running |
to_in_progress | hook.to_running | awaiting_review -> running |
tool_use | agent.tool_use | awaiting_review -> running |
prompt_ready | agent.prompt_ready | running -> awaiting_review |
to_review | hook.to_review | running -> awaiting_review |
awaiting_review | hook.to_review | running -> awaiting_review |
exit | process.exit | running/awaiting_review -> completed |
completed | process.exit | running/awaiting_review -> completed |
exit_error | process.exit_error | running/awaiting_review -> failed |
failed | process.exit_error | running/awaiting_review -> failed |
requeue | user.requeue | completed/failed -> idle |
Auto-Registration
If a start event arrives for an unknown tmux_session, the system automatically creates a new session entry in the StateHub. Similarly, any event from a tmux session with an agent- prefix auto-registers the session and transitions it to running.
Additional Hook Endpoints
List Sessions
GET /api/hooks/sessionsReturns all tracked sessions. Unauthenticated (localhost only).
Register Session
POST /api/hooks/sessions
Content-Type: application/json{
"agent_type": "claude-code",
"tmux_session": "agent-my-task",
"label": "My Task",
"prompt": "Fix the bug",
"task_id": "task-uuid",
"depends_on": ["other-task-uuid"]
}Manually registers a session. Returns the existing session if one already exists for that tmux name.
Update Session State
PATCH /api/hooks/sessions/:id
Content-Type: application/json{
"state": "awaiting_review"
}Used by the board's drag-and-drop to change session state. Accepts either a state (mapped to the corresponding event) or a direct event name.
Delete Session
DELETE /api/hooks/sessions/:idRemoves a session and cleans up any session-specific hook artifacts.
Claude Code Hook Installation
When the agent launcher starts a Claude Code session, it installs hook scripts in ~/.claude/settings.json. These hooks fire curl commands to /api/hooks/ingest on the following lifecycle events:
| Claude Code Hook | Event sent |
|---|---|
PostToolUse | tool_use |
Notification | to_review |
UserPromptSubmit | prompt_ready |
Stop | exit |
The hooks read $OPTIMALOS_SESSION_TMUX from the tmux session environment to identify which session the event belongs to. Sessions without this variable are silently skipped (exit 0).
OpenClaw Integration
The OptimalOS server maintains a WebSocket client connection to the OpenClaw gateway on port 18789 (configurable via OPENCLAW_GATEWAY_PORT). OpenClaw agent events are streamed over this connection and routed to the correct session in the StateHub.
Hook Queue
Failed hook events are written to a file-backed retry queue at ~/.optimalos/hook-queue.jsonl. The queue drains every 5 seconds. Events that fail 5 times are dead-lettered.
Error Handling
The endpoint returns standard HTTP status codes:
| Code | Meaning |
|---|---|
200 | Event accepted and processed |
400 | Invalid JSON, missing event field, or unknown event type |
404 | Session not found (and not auto-registerable) |