What the agent writes
init owns only its own keys in each file and preserves everything else.
remove strips exactly those keys again. Examples below use the default
endpoint http://localhost:8787.
Claude Code (~/.claude/settings.json)
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_METRICS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:8787",
"OTEL_LOG_TOOL_DETAILS": "1"
},
"hooks": {
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "costpilot-agent hook --tool claude-code --endpoint http://localhost:8787" }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "costpilot-agent hook --tool claude-code --event stop --endpoint http://localhost:8787" }] }
]
}
}
The Stop hook reports the end of a turn, so a branch created mid-turn gets
the whole turn. It is always registered; the hook only sends the turn-end
report to servers whose agent config says turnEndReports: true, because an
older server would take it for another prompt. The other tools get the same
pair on their own events: Gemini CLI BeforeAgent/AfterAgent, Codex CLI
UserPromptSubmit/Stop, Cursor beforeSubmitPrompt/stop.
The env block makes Claude Code export usage logs and metrics (OTLP over
HTTP/JSON) to the Costpilot API. OTEL_LOG_TOOL_DETAILS=1 adds tool and MCP
server names, which the dashboard's tool-usage card needs. With --token,
"OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer <secret>" is added
and --token is appended to the hook command. Endpoint and token are on the
command because the settings env does not reliably reach hook processes (it
does not when Claude Code runs inside the VS Code extension). An older
costpilot-agent hook entry without arguments falls back to the
OTEL_EXPORTER_OTLP_* env and is upgraded in place on the next init.
With --project the same variables go into ./.envrc as
export KEY="value" lines instead, and ./.claude/settings.json only gets
the hook: Claude Code ignores telemetry env in project settings. See
Report only some projects.
Gemini CLI (~/.gemini/settings.json)
{
"telemetry": {
"enabled": true,
"target": "local",
"otlpEndpoint": "http://localhost:8787",
"otlpProtocol": "http",
"useCollector": false,
"logPrompts": false
},
"hooks": {
"BeforeAgent": [
{ "hooks": [{ "type": "command", "command": "costpilot-agent hook --tool gemini --endpoint http://localhost:8787", "name": "costpilot", "timeout": 3000 }] }
],
"AfterAgent": [
{ "hooks": [{ "type": "command", "command": "costpilot-agent hook --tool gemini --event stop --endpoint http://localhost:8787", "name": "costpilot", "timeout": 3000 }] }
]
}
}
Gemini appends /v1/logs and /v1/metrics to the base URL itself.
logPrompts is forced off: prompt text is not needed for cost attribution.
The telemetry block has no header setting, so with --token the agent
writes OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <secret>" to
~/.gemini/.env (./.gemini/.env with --project; Gemini loads it at
startup) and appends --token to the hook command. Gemini uses the first
.env it finds walking up from the project directory, so a different .env
of your own can shadow the one the agent wrote; add the variable there in
that case.
Codex CLI (~/.codex/config.toml + ~/.codex/hooks.json)
[otel]
environment = "production"
log_user_prompt = false
[otel.exporter.otlp-http]
endpoint = "http://localhost:8787/v1/logs"
protocol = "json"
Only the [otel] table (and its sub-tables) is owned by init; the rest of
config.toml is preserved (only blank lines around the [otel] block may
change). With --token an [otel.exporter.otlp-http.headers] sub-table
carries Authorization = "Bearer <secret>". The hooks go into
~/.codex/hooks.json:
{
"hooks": {
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "costpilot-agent hook --tool codex --endpoint http://localhost:8787", "statusMessage": "Costpilot: mapping session to issue", "timeout": 3 }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": "costpilot-agent hook --tool codex --event stop --endpoint http://localhost:8787", "timeout": 3 }] }
]
}
}
Cursor (~/.cursor/hooks.json)
{
"version": 1,
"hooks": {
"beforeSubmitPrompt": [
{ "command": "costpilot-agent hook --tool cursor --endpoint http://localhost:8787", "timeout": 3 }
],
"stop": [
{ "command": "costpilot-agent hook --tool cursor --event stop --endpoint http://localhost:8787", "timeout": 3 }
]
}
}
Cursor has no OpenTelemetry export and its hook payloads carry neither model
nor token counts, so the agent can only deliver the session → issue mapping
(conversation_id, first workspace_root). Cursor parses the hook's
answer, so this is the one adapter that prints: {"continue":true} on a
prompt, {} on stop. Token usage for Cursor would
have to come from the Cursor Admin API (Teams/Enterprise), which is a
server-side integration and not part of Costpilot yet.