Troubleshooting
The hook shows nothing in the tool and always exits 0, even when the server
rejects the request, so a broken setup is silent by design. Start with the quick checks,
then run check, then go step by step.
Quick checks
which costpilot-agentmust print a path. If not, the agent is not installed globally: repeat Step 1.- Did you restart your tools after
init? Settings and hooks are read at startup. - Is your branch name carrying an issue key? Without one the session
counts as "No issue"; the branch name is kept in the
branchdimension. - Did you give the telemetry up to a minute to arrive? Exporters batch.
- Is the dashboard's date range including today?
Run check in the project directory
cd ~/code/my-project
costpilot-agent check
check covers every tool configured for the project or globally (--tool
to pick one), says which scope is in effect and warns when a global config
makes other projects report too. Each line is one of the checks below.
✗ lines make the command exit 1; ! lines are warnings.
| Line | What it verifies | If it fails |
|---|---|---|
costpilot-agent is on the PATH | The tool can spawn the hook | Install globally, or npm link a checkout |
project config / global config | Which settings file is in effect | Run init in the right scope |
telemetry: on → URL | Export is configured and points at your server | Re-run init --endpoint … |
hook: … → URL/v1/session-map | The prompt hook is registered | Re-run init |
branch … → issue … | What the hook would report for the current branch | Rename the branch or adjust the issue key pattern |
server: accepted a test mapping (HTTP 200) | A real POST /v1/session-map with your endpoint and token | See 401 and connection errors |
<Tool> ran the hook N min ago: … | The tool itself executed the hook | Restart the tool and send one prompt |
! turn-end hook: missing under <Event> | The agent was upgraded but the tool's settings predate the turn-end hook | Re-run init (or sync --project for a project setup) |
! hook: old command without --endpoint / ! hook: posts to X, telemetry goes to Y | Hook and telemetry disagree about the server | Re-run init --endpoint … |
✗ not a git repository / ✗ no branch checked out | The hook has nothing to derive an issue from | Run check inside a repository on a branch |
✗ settings file is invalid | A tool's settings file is not valid JSON/TOML | Fix the file by hand, then init again |
The last line reads a receipt the hook leaves in ~/.costpilot/hook-runs/
on every prompt run (time, issue, HTTP status, never the token; turn-end
runs leave none). ! not seen yet means the tool has not run the hook here
yet. With a global setup check also prints a ! line saying that every
project reports; that is a reminder, not an error.
The server answers 401
The server sets COSTPILOT_INGEST_TOKEN and init was run without, or with
the wrong, --token. Re-run init with the right token; the tools' settings
and the hook command are updated together. Dashboard accounts are a separate
thing: /v1/* is never behind the sign-in, and the bearer token is the only
credential the agent needs.
The server is unreachable
Try the same request the hook makes:
curl -i -X POST https://costpilot.example.com/v1/session-map \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <secret>' \
-d '{"session_id":"test-1","issue_id":"PROJ-123","branch":"feature/PROJ-123-test","repo":"owner/name","agent":"claude-code"}'
A 200 with {} means the server is fine and the problem is on the tool
side. A connection error means the endpoint is wrong or the server is down.
The endpoint is always the API base URL without a path: the hook appends
/v1/session-map itself, the tools append /v1/logs and /v1/metrics.
Cost arrives but is not attributed to the issue
The telemetry shows up under the actor and model, but the Issue tile shows the session under "No issue":
- The report must reach the server before the usage events it should apply to. The hook runs on every prompt, so after the first prompt everything that follows is attributed. Calls that happened before the first report of a session stay unattributed.
- The hook ran but on the wrong branch: check the
branch … → issueline ofcheck. A branch created mid-turn is handled by the turn-end report, which only servers withturnEndReports: truereceive. - The key does not match the pattern: the default issue key pattern and the project patterns live in the Admin Area. The whole key must match a project pattern for the project to be derived from it.
Claude Code project setup exports nothing
Claude Code ignores CLAUDE_CODE_ENABLE_TELEMETRY and the OTEL_*
variables in project-level settings. A project setup therefore relies on
direnv and ./.envrc. check warns when the shell
you run it in does not have the variables (direnv not hooked in, or direnv allow missing), and fails when the last hook run did not have them either:
Claude Code was started outside a direnv shell. Start claude from a shell
inside the project; in VS Code use the direnv extension or launch the editor
with code . from that shell. See
Report only some projects.
Tool usage card stays empty
Claude Code's built-in tool names arrive without extra settings; MCP server
names need OTEL_LOG_TOOL_DETAILS=1. init sets it unless the admin
switched off Agent → Log tool details; check the env block of
~/.claude/settings.json (or ./.envrc for a project setup). Gemini CLI
tool calls and Codex CLI tool results are counted as well; Cursor sends no
telemetry at all.
Leftover hooks from an earlier setup
init replaces its own hook entries and leaves everything else untouched. A
hook from an earlier prototype (for example a node .../hooks/map-session.js
line pointing at localhost:4318) keeps running next to the new one and has
to be removed from the UserPromptSubmit block by hand.
Capture raw telemetry
To see exactly what a tool sends, including attributes ingestion drops, set
COSTPILOT_OTLP_DUMP on the server to a file path. Every /v1/logs and
/v1/metrics request is appended to it as received, one JSON line per
request ({ receivedAt, kind, payload }), before any normalization:
COSTPILOT_OTLP_DUMP=../otlp-dump.jsonl npm run dev # bash
$env:COSTPILOT_OTLP_DUMP="..\otlp-dump.jsonl"; npm run dev # PowerShell
Relative paths resolve against the API's working directory (apps/api).
The payloads contain user identities and, with OTEL_LOG_TOOL_DETAILS=1,
commands and file paths: keep the file outside the repository.
A tool exports its telemetry to one endpoint only. To capture from your own
machine, point the agent at the local instance and back again afterwards;
init keeps the configured token unless you pass a new one, and the tools
pick up the change on their next start:
costpilot-agent init --endpoint http://localhost:8787 # to the local instance
costpilot-agent init --endpoint https://costpilot.example.com # back to the usual server
costpilot-agent check # shows where each tool reports
Run the hook by hand
On a branch with an issue id:
echo '{"session_id":"test-1","cwd":"'$PWD'"}' | costpilot-agent hook --tool claude-code --endpoint https://costpilot.example.com --token <secret>
Then watch the dashboard or the server logs for session test-1. Each tool
has its own payload shape; Cursor for example expects conversation_id and
workspace_roots. Two side effects: the report creates a real turn for
session test-1 on the server, and the run leaves a receipt, so the next
check reports it as a hook run of Claude Code.