Skip to content

Cline provider misses every Cline CLI session (~/.cline/data/sessions, cline@3.0.49) — silently reported as $0.00 #874

Description

@therickfactr

Summary

The cline provider silently reports zero usage for sessions created by the Cline CLI (npm cline, currently 3.0.49). It only discovers the VS Code extension's tasks/<taskId>/ui_messages.json layout. The CLI writes a completely different layout — ~/.cline/data/sessions/<sessionId>/ — which nothing in codeburn reads.

There is no warning and no partial data — --verbose says nothing either. The provider is registered and "supported", so the report looks correct while the tokens are simply missing.

On this machine that is 5 sessions from a single afternoon: 174,544 input / 10,202 output / 127,410 cache-read tokens, $0.035 — invisible.

Environment

codeburn main @ 2de4d100bf746b0830b1d43c6118a91d853628b5 (0.9.19)
Cline CLI cline@3.0.49 (npm, global)
OS macOS 26.6 (25G72), arm64
Node v24.18.0
History size du -sh ~/.cline/data → 5.2M (sessions/ → 292K)

What codeburn scans today

src/providers/cline.ts:55-58 builds exactly two roots:

const baseDirs = configuredDirs ?? [
  getVSCodeGlobalStoragePath(EXTENSION_ID),  // saoudrizwan.claude-dev
  getClineDataPath(),                        // ~/.cline/data
]

Both are handed to discoverClineTasks, which per docs/providers/cline.md includes a task only when it has a ui_messages.json — i.e. it requires <root>/tasks/<taskId>/ui_messages.json.

~/.cline/data/tasks/ does not exist on a CLI install. There is no tasks/ directory at all.

What the Cline CLI actually writes

~/.cline/data/
  sessions/<sessionId>/
    <sessionId>.json           # session metadata + rolled-up usage
    <sessionId>.messages.json  # per-message metrics
  db/sessions.db               # SQLite mirror of the same rows (WAL)
  settings/  locks/  cache/  logs/

<sessionId>.json (redacted, real file):

{
  "version": 1,
  "session_id": "1785701058566_vnwtz",
  "source": "cli",
  "started_at": "2026-08-02T20:04:18.628Z",
  "ended_at": "2026-08-02T20:08:27.768Z",
  "status": "completed",
  "exit_code": 0,
  "interactive": false,
  "provider": "cline-pass",
  "model": "z-ai/glm-5.2",
  "cwd": "/Users/<user>/dev/<org>/<repo>",
  "workspace_root": "/Users/<user>/dev/<org>/<repo>",
  "metadata": {
    "git": { "url": "<redacted>", "branch": "main" },
    "title": "<first line of prompt>",
    "totalCost": 0.0268881228,
    "usage": {
      "inputTokens": 169061,
      "outputTokens": 10069,
      "cacheReadTokens": 127360,
      "cacheWriteTokens": 0,
      "totalCost": 0.0268881228
    }
  },
  "messages_path": "/Users/<user>/.cline/data/sessions/1785701058566_vnwtz/1785701058566_vnwtz.messages.json"
}

<sessionId>.messages.json{ version, updated_at, agent, sessionId, messages[], system_prompt }, where each assistant message carries its own metrics:

{
  "id": "msg_ZLpzRugU",
  "role": "assistant",
  "content": "<trimmed>",
  "ts": 1785701064304,
  "modelInfo": { "id": "z-ai/glm-5.2", "provider": "cline-pass" },
  "metrics": {
    "inputTokens": 6937,
    "outputTokens": 213,
    "cacheReadTokens": 0,
    "cacheWriteTokens": 0,
    "cost": 0.002108502
  }
}

The SQLite mirror at ~/.cline/data/db/sessions.db has one sessions row per session with the same fields plus parent_session_id, parent_agent_id, agent_id, is_subagent, conversation_id, metadata_json, messages_path.

Everything codeburn needs — provider, model, timestamps, project path, git branch, per-call token splits, cache reads/writes, cost — is already there. It is just never opened.

If you want to confirm the layout without my machine: npm i -g cline@3.0.49 and run one prompt, or grep the shipped bundle — node_modules/@cline/core/dist/index.js contains the sessions path construction, the <id>.messages.json filename, and the messages_path field. Note that CLINE_DIR and CLINE_DATA_DIR relocate the root, so discovery should respect them rather than hardcoding ~/.cline (there is also CLINE_TEAM_DATA_DIR for team mode).

Repro

$ ls ~/.cline/data/sessions | wc -l
5
$ npx tsx src/cli.ts export --format json --provider cline --from 2026-08-01 --to 2026-08-02 -o /tmp/cline.json
  Exported (2026-08-01 to 2026-08-02) to: /tmp/cline.json
$ python3 -c "import json;d=json.load(open('/tmp/cline.json'));print(d['summary']);print(len(d['sessions']),'sessions',len(d['records']),'records')"
[{'Period': '2026-08-01 to 2026-08-02', 'Cost (USD)': 0, 'Saved (USD)': 0, 'API Calls': 0, 'Sessions': 0, 'Projects': 0}]
0 sessions 0 records

Ground truth for the same window, read straight from the session metadata:

$ sqlite3 ~/.cline/data/db/sessions.db \
  "select session_id, json_extract(metadata_json,'$.usage.inputTokens'),
          json_extract(metadata_json,'$.usage.outputTokens'),
          json_extract(metadata_json,'$.totalCost') from sessions;"
1785699299865_ynuy2|5483|133|0.0081984
1785700832224_ss0xl|0|0|0
1785700997640_0bvrp|0|0|0
1785701013051_rhwoy|0|0|0
1785701058566_vnwtz|169061|10069|0.0268881228

Totals: 174,544 in / 10,202 out / $0.0351 — reported by codeburn as $0.00.

Minimal discovery probe

Plants four fixtures under a temp $HOME and asks the provider what it finds. Run from the repo root with npx tsx:

import { mkdirSync, writeFileSync, rmSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'

const HOME = join(tmpdir(), 'cb-cline-probe')
rmSync(HOME, { recursive: true, force: true })
process.env.HOME = HOME

const EXT = 'saoudrizwan.claude-dev'
const uiMsgs = JSON.stringify([{ ts: 1785701064304, type: 'say', say: 'api_req_started',
  text: JSON.stringify({ tokensIn: 100, tokensOut: 10, cost: 0.001, request: 'x' }) }])
const apiHist = JSON.stringify([{ role: 'assistant', content: [{ type: 'text', text: 'hi' }] }])

// A) extension/tasks layout in all three VS Code variants
for (const [variant, taskId] of [['Code','task-stable'],['Code - Insiders','task-insiders'],['VSCodium','task-vscodium']] as const) {
  const d = join(HOME, 'Library', 'Application Support', variant, 'User', 'globalStorage', EXT, 'tasks', taskId)
  mkdirSync(d, { recursive: true })
  writeFileSync(join(d, 'ui_messages.json'), uiMsgs)
  writeFileSync(join(d, 'api_conversation_history.json'), apiHist)
}

// B) Cline CLI 3.0.49 layout
const sid = '1785701058566_vnwtz'
const sd = join(HOME, '.cline', 'data', 'sessions', sid)
mkdirSync(sd, { recursive: true })
writeFileSync(join(sd, `${sid}.json`), JSON.stringify({ version: 1, session_id: sid, source: 'cli',
  status: 'completed', provider: 'cline-pass', model: 'z-ai/glm-5.2', cwd: HOME, workspace_root: HOME,
  started_at: '2026-08-02T20:04:18.628Z', ended_at: '2026-08-02T20:08:27.768Z',
  metadata: { totalCost: 0.0268881228, usage: { inputTokens: 169061, outputTokens: 10069,
    cacheReadTokens: 127360, cacheWriteTokens: 0, totalCost: 0.0268881228 } },
  messages_path: join(sd, `${sid}.messages.json`) }))
writeFileSync(join(sd, `${sid}.messages.json`), JSON.stringify({ version: 1, agent: 'lead', sessionId: sid,
  messages: [{ id: 'msg_1', role: 'assistant', content: 'hi', ts: 1785701064304,
    modelInfo: { id: 'z-ai/glm-5.2', provider: 'cline-pass' },
    metrics: { inputTokens: 6937, outputTokens: 213, cacheReadTokens: 0, cacheWriteTokens: 0, cost: 0.002108502 } }] }))

const { createClineProvider } = await import('./src/providers/cline.js')
for (const f of await createClineProvider().discoverSessions()) console.log('-', f.path.replace(HOME, '~'))

Actual output — 4 fixtures planted, 1 discovered:

- ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/tasks/task-stable

Secondary finding: only the stable VS Code variant is scanned

Visible in the same probe run, and independent of the CLI issue — happy to split this into its own issue if you prefer.

src/providers/cline.ts:56 calls the singular getVSCodeGlobalStoragePath(EXTENSION_ID), which returns only paths[0] (vscode-cline-parser.ts:41-43), and then passes it as an explicit overrideDir. That bypasses getVSCodeGlobalStoragePaths(), which would also have returned Code - Insiders and VSCodium (vscode-cline-parser.ts:15-31).

Sibling providers do not do this — roo-code.ts:20 and kilo-code.ts:38 pass overrideDir straight through (undefined by default), so they scan all three. Swapping the extension id in the probe above to rooveterinaryinc.roo-cline and calling createRooCodeProvider() finds 3 of 3 with identical fixtures, versus Cline's 1 of 3.

So for Cline users on Insiders or VSCodium, the globalStorage copy is missed entirely. Whether that means zero data depends on whether their Cline version also mirrors tasks into ~/.cline/data/tasks/, which is still scanned — so this is "silently partial" rather than guaranteed-empty, and it varies by version.

Worth noting because #230 asked for VSCodium support and getVSCodeGlobalStoragePaths duly covers it — Cline is just the one provider that routes around it.

Suggested fix

Deferring to your judgement on shape — two options:

  1. Third root inside the existing cline provider. discoverSessions already merges roots and dedups by task id; a sessions/-layout branch would need its own parser, so the provider stops being a thin wrapper over the shared Cline-family parser. There is precedent — kilo-code.ts:38-46 already merges discoverClineTasks with discoverSqliteSessions in one provider and dispatches to the matching parser per source.
  2. A separate cline-cli provider. Keeps vscode-cline-parser.ts untouched (docs/providers/cline.md explicitly asks not to fork it while Roo Code / KiloCode share it), at the cost of splitting one tool across two provider names in reports and filters.

I lean toward (2) for parser hygiene, but (1) if you would rather users see a single cline row.

Either way, notes from the data:

  • JSON over SQLite. sessions.db is live and WAL-mode; the per-session JSON is self-contained and needs no native dep. sqlite-session-parser.ts exists if you would rather go the DB route, but the JSON path avoids the lazy-load requirement in CONTRIBUTING.md for native deps.
  • The metadata file alone gives session-level totals, so a cheap first cut is possible without parsing messages; per-call records need <sessionId>.messages.json.
  • Subagents are already modelledis_subagent, parent_session_id, parent_agent_id — which lines up with the subagent-fold work in recent PRs.
  • Model strings are inconsistent across sessions from the same run: z-ai/glm-5.2, cline-pass/glm-5.2, and bare GLM-5.2 all appear in my five sessions. Whatever normalization you choose will need to survive that, or pricing lookups will miss.
  • provider is the upstream LLM route, not the tool (cline-pass here), so it should not be confused with codeburn's own provider name.

Prior art

#130 asked for Cline support generically and was closed 2026-05-16; #312 implemented the extension layout and #836 refactored it into core. All three predate the CLI layout, and I could not find an existing issue covering it. Shape-wise this is the same class as #850, #873, and #626 — discovery missing a real on-disk session variant.

Per CONTRIBUTING.md I am commenting before coding rather than sending an unsolicited PR: I use the Cline CLI daily and have real sessions to test against, so I am happy to implement whichever shape you prefer, with a fixture-based test under tests/providers/. Just say which.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions