Skip to content

Cursor project based attribution broken #601

Description

@Enclavet

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions