Issue: Cursor usage all bucketed under cursor project — composer.composerData key renamed to composer.composerHeaders
Summary
On recent Cursor builds, all Cursor usage is attributed to the single catch-all cursor project instead of the real workspace. Per-project breakdown and --project filtering are effectively broken for Cursor.
Environment
- codeburn: v0.9.15
- Cursor: recent build (Windows),
state.vscdb schema described below
- Node: 22.x
Expected behavior
Cursor sessions attributed to their workspace, e.g. -Users-me-projects-myapp (Claude-style slug), so codeburn report --provider cursor --project myapp matches.
Actual behavior
Every composer lands in the cursor orphan bucket:
codeburn report -p all --provider cursor --format json | jq '.projects[].name'
"cursor"
…even when run from inside a real project directory with active Cursor workspaces.
Root cause
loadWorkspaceMap() in src/providers/cursor.ts builds the composerId → workspace folder map by reading:
SELECT value FROM ItemTable WHERE key='composer.composerData'
and iterating allComposers[].composerId.
Recent Cursor builds renamed this key from composer.composerData to composer.composerHeaders (identical { allComposers: [{ composerId }] } shape). On these builds the query returns no rows, the map is built empty, and every composer falls through to ORPHAN_PROJECT = 'cursor'.
Token parsing is unaffected (bubbles still read fine) — only the workspace mapping key moved, which is why totals are correct but mis-attributed.
Evidence
Inspected a real workspace state.vscdb:
ItemTable['composer.composerData'] — absent
ItemTable['composer.composerHeaders'] — present, allComposers.length = 8
- Those 8
composerIds match the composerData:<id> rows and the bubbleId:<composerId>:… keys in cursorDiskKV that carry the usage.
Suggested fix
Read both keys in loadWorkspaceMap() and merge their allComposers lists (keep composer.composerData for older installs):
SELECT value FROM ItemTable
WHERE key IN ('composer.composerData', 'composer.composerHeaders')
Iterate every returned row's allComposers. Map.set per composerId is idempotent, so reading both is a harmless union.
Verified against the real DB: with this change, 21/22 calls attribute to the actual workspace project instead of cursor (1 residual orphan is a draft/tool-call sub-composer, correctly unmapped).
PR
Fix + regression test (covers the new key and the legacy key): #599
Issue: Cursor usage all bucketed under
cursorproject — composer.composerData key renamed to composer.composerHeadersSummary
On recent Cursor builds, all Cursor usage is attributed to the single catch-all
cursorproject instead of the real workspace. Per-project breakdown and--projectfiltering are effectively broken for Cursor.Environment
state.vscdbschema described belowExpected behavior
Cursor sessions attributed to their workspace, e.g.
-Users-me-projects-myapp(Claude-style slug), socodeburn report --provider cursor --project myappmatches.Actual behavior
Every composer lands in the
cursororphan bucket:…even when run from inside a real project directory with active Cursor workspaces.
Root cause
loadWorkspaceMap()insrc/providers/cursor.tsbuilds thecomposerId → workspace foldermap by reading:and iterating
allComposers[].composerId.Recent Cursor builds renamed this key from
composer.composerDatatocomposer.composerHeaders(identical{ allComposers: [{ composerId }] }shape). On these builds the query returns no rows, the map is built empty, and every composer falls through toORPHAN_PROJECT = 'cursor'.Token parsing is unaffected (bubbles still read fine) — only the workspace mapping key moved, which is why totals are correct but mis-attributed.
Evidence
Inspected a real workspace
state.vscdb:ItemTable['composer.composerData']— absentItemTable['composer.composerHeaders']— present,allComposers.length = 8composerIds match thecomposerData:<id>rows and thebubbleId:<composerId>:…keys incursorDiskKVthat carry the usage.Suggested fix
Read both keys in
loadWorkspaceMap()and merge theirallComposerslists (keepcomposer.composerDatafor older installs):Iterate every returned row's
allComposers.Map.setpercomposerIdis idempotent, so reading both is a harmless union.Verified against the real DB: with this change, 21/22 calls attribute to the actual workspace project instead of
cursor(1 residual orphan is a draft/tool-call sub-composer, correctly unmapped).PR
Fix + regression test (covers the new key and the legacy key): #599