--- title: How detection works description: How ClawMetry finds agent runtimes — filesystem probes rather than PATH lookups, discovery order, environment overrides, and why one can be detected but empty. keywords: agent runtime detection, ClawMetry adapters, agent data directory discovery eyebrow: 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: 1. **An explicit ClawMetry override** — `CLAWMETRY__DIR`, `CLAWMETRY__DB` or similar. This exists so you can point ClawMetry at a relocated store without touching the runtime's own configuration. 2. **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. 3. **Platform conventions** — XDG on Linux and, for some runtimes, macOS; `%APPDATA%` on Windows. 4. **The documented default** — `~/.codex`, `~/.claude`, `~/.grok`, and so on. The exact chain per runtime is on that runtime's [reference page](/docs/runtimes/overview/). :::warning 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 `-wal` sidecar. 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. ```bash CLAWMETRY_FAMILY_SESSION_LIMIT=500 clawmetry sync --restart ``` `CLAWMETRY_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 | ```bash title="Check it" 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.