Runtimes
How detection works#
Detection is a filesystem question, not a PATH question. ClawMetry asks "did
this runtime write a store here?", because a binary on your path that you have
never run tells you nothing, and a runtime you uninstalled last month may still
have a year of history worth reading.
The order#
Every adapter resolves its data location the same way, most specific first:
- An explicit ClawMetry override —
CLAWMETRY_<RUNTIME>_DIR,
CLAWMETRY_<RUNTIME>_DB or similar. This exists so you can point ClawMetry
at a relocated store without touching the runtime's own configuration.
- The runtime's own environment variable —
CODEX_HOME,KIMI_SHARE_DIR,
OPENHANDS_PERSISTENCE_DIR, CLINE_DATA_DIR, and so on. If you already
moved the runtime, ClawMetry follows.
- Platform conventions — XDG on Linux and, for some runtimes, macOS;
%APPDATA% on Windows.
- The documented default —
~/.codex,~/.claude,~/.grok, and so on.
The exact chain per runtime is on that runtime's reference page.
Platform assumptions bite
Goose uses XDG paths on macOS, not ~/Library/Application Support — it
follows the CLI convention rather than the app convention. Cline's data lives
under ~/.cline/**data**, not ~/.cline. GEMINI_CLI_HOME points at the
directory that contains .gemini, while QWEN_HOME points at the data
directory itself. Each of these has silently returned "no sessions" for somebody.
Two runtimes are not on the filesystem#
- QM persists to PostgreSQL. ClawMetry reads whatever
DATABASE_URL(or
CLAWMETRY_QM_DATABASE_URL, for a read replica) points at, using short-lived
read-only connections. With no Postgres driver installed, detection reports
not-detected with a note rather than failing.
- Exo anchors its state to the workspace, not the home directory — it is
the only runtime in the matrix that does. Discovery scans a bounded set of
likely parent directories for */.exo/exoharness/agents, honours
CLAWMETRY_EXO_ROOTS, and caches the result so a page load does not rescan
your disk.
Aider has a related constraint: its transcript is written into each
project, and there is no central store and no home-anchored environment
variable. ClawMetry scans a bounded set of likely roots plus
AIDER_HISTORY_DIRS. It deliberately does not walk your entire home directory.
Reading is always read-only#
Every adapter opens files read-only and never writes into an agent's directory. For SQLite stores this matters more than it sounds:
- A runtime that is running keeps recent writes in a
-walsidecar.
Opening with immutable=1 ignores the WAL and returns a stale or empty view.
Cursor, Devin, n8n, Copilot and Hermes are all read WAL-aware (mode=ro) for
exactly this reason.
- A runtime that owns its files exclusively — NanoClaw's per-session
databases — is opened mode=ro&immutable=1 so ClawMetry can never take a
writer lock on them.
Getting this backwards is the difference between "my live session is invisible" and "my agent hung on a locked database". Both have happened; both are why the per-runtime pages call it out.
How much history gets ingested#
CLAWMETRY_FAMILY_SESSION_LIMIT (default 50) caps how many recent sessions
each runtime contributes on a pass. Sessions are ranked newest-first by file
mtime, so you always get the current work; raise it for deeper backfill.
CLAWMETRY_FAMILY_SESSION_LIMIT=500 clawmetry sync --restartCLAWMETRY_FAMILY_EVENT_CAP bounds the events pulled per session, which is
what keeps one enormous session from starving a sync cycle.
Detected but empty#
This is a real state and it usually means one of four things:
| Symptom | Likely cause |
|---|---|
| Detected, zero sessions | The runtime has a store but has not written anything yet |
| Detected, only old sessions | CLAWMETRY_FAMILY_SESSION_LIMIT is hiding newer ones, or the daemon has not run a full cycle |
| Not detected, but installed | Store relocated — set the override on the runtime's page |
| Qwen Code detected, nothing recorded | Chat recording is opt-in upstream (--chat-recording); nothing exists to read |
clawmetry runtimes --json | jq '.runtimes[] | {id, detected, meta}'
clawmetry status --json | jq '{daemon, last_sync, store_bytes}'Deduplication#
Some work is visible from two places, and counting it twice would be worse than missing it:
- Claude Code sessions spawned by OpenClaw are recorded under the OpenClaw
session UUID. The standalone Claude Code adapter reads OpenClaw's index and skips those ids.
- OpenHands stores a copy of each child conversation's metrics under a
delegate:* key on the parent. Summing every key and listing the child
would double-count, so those keys are excluded from the parent row.
- Copilot stamps sub-agent API calls with
initiator='sub-agent', so child
spend is split out of the parent rollup rather than added to it.
- Gemini CLI re-appends a turn under the same message id once its tool calls
resolve, with the same token block both times. Events are de-duplicated by message id, last write wins.
Adding a runtime to the loader#
For contributors: an adapter module is inert until it is named in the daemon's adapter spec tuple and in the entitlement runtime list. There is no dynamic discovery — a module sitting in the adapters package that nobody registered will never be imported, and a runtime missing from the entitlement list is invisible to the UI even when its adapter runs.