Skip to main content

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.