Agent chat DB (`state.vscdb`) grows without bound — ~50 bubble rows per visible message, 96 GB and climbing

Heavy agent use grows state.vscdb without bound, and past some size it degrades
the whole app into renderer crashes and failed subagent dispatch with nothing
anywhere naming the cause. I spent an evening diagnosing this and think the
underlying cause is a retention bug rather than my usage, so here’s what I found.

Setup: macOS, heavy agent + subagent use, several long-running agent chats
open across days.

The symptoms, none of which point at the database

  • Repeated renderer/window crashes with no consistent trigger. Each one kills
    every agent running in that window.
  • Subagent dispatch failing with Error: Timeout waiting for bubble creation.
    This is the only symptom that points at a write.
  • Subagents stuck at “Starting up” forever, accumulating into the hundreds —
    four chats held roughly 127, 66, 23, and 100 of them. Each appears to be a
    half-created bubble record left behind by a timed-out write.
  • ~82 GB consumed by a single file, which is easy to miss if you’re looking at
    project directories.

The cause: ~50 stored rows per visible message

Everything lives in ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb,
table cursorDiskKV, keys bubbleId:*. At diagnosis that was ~82 GB across
7.47M rows, 5.94M of them bubbleId:*
.

The ratio is the part I’d flag as a bug. Individual chats showed on the order of
40,000 messages in their own header list against ~2,200,000 bubbleId rows —
roughly 50 rows per visible message.
That looks like every superseded revision
of every bubble being retained as an agent streams and edits it, with the dead
ones never pruned.

It’s also extremely concentrated rather than spread across history. Of 6,897,718
bubbleId rows, three chats held 5,858,083 — 85%. Fourth place was 158K, 9x
smaller than third. All three were long-running agent chats still being written
to. So an ordinary long chat is fine; what’s expensive is a single chat staying
open across days of high-volume agent writing.

The regrowth rate, which is why this isn’t a one-off

I’ve been sampling the file since the cleanup. From 2026-09-01 22:18 to
2026-09-03 15:17 — about 41 hours — it went from 67.61 GiB to 89.66 GiB, +22.05
GiB, averaging ~0.54 GiB/hour
under continuous agent use. The file now reads
96,277,479,424 bytes (96.3 GB / 89.7 GiB), which is larger than the size
that triggered the original incident
, reached in under two days.

Growth is bursty rather than steady — hourly buckets ranged about 10x, and the
largest single 2-minute interval added 666 MB — so it’s individual large blobs
landing rather than gradual accretion.

The remedy exists but is hard to find and harder to trust

The fix is an undocumented Command Palette command, GC Agent KV Blobs. Three
things about it each cost me real time:

  1. It only reclaims orphans. It took the file from ~82 GB to ~66.66 GB — 18%.
    The remaining 66 GB was live content, and I could find no retention lever for
    it anywhere in the product. Deleting a chat in the UI doesn’t visibly reclaim
    its blob storage.
  2. It takes hours with no honest progress signal. The Scanning composer data
    phase shows a long counter that reads as the main event, but slower phases
    follow it — so a nearly-complete counter means nearly none of the work is
    done. My first run also crashed partway through; it completed on a second
    attempt.
  3. The write-ahead log actively misleads. Rewriting a 66 GB database produced
    a 66.59 GB -wal file, so free disk space did not move at all until a
    checkpoint. A window reload does not checkpoint — only a full application quit
    does — and that quit presents as a hang: windows gone, process still in
    alt-tab, Activity Monitor reporting “not responding”, for as long as the merge
    takes. The two natural readings of that state are “the GC failed” and “Cursor
    is hung, force-kill it”. Both are wrong, and force-killing there looks like a
    good way to corrupt the database.

One useful diagnostic for anyone else landing here: freelist_count is not the
right instrument. It measures space already freed inside the file, so it reads
near zero whether or not collectable garbage exists. The probe that actually
answers “will a GC help?” is counting bubbleId:* rows whose composer has no
surviving composerData:* row.

What I’d ask for

  1. Bound the table, or expose retention — age-based pruning, or a per-chat
    delete that actually reclaims blob storage. Pruning superseded bubble
    revisions would address the 50x ratio at its source.
  2. Surface the condition before it degrades the app — a warning at some size
    threshold beats leaving it to present as unexplained crashes.
  3. Make the GC discoverable, with a truthful progress indicator that includes
    the checkpoint phase and warns that the quit will look like a hang.
  4. Fail loudly on a bubbleId write timeout rather than leaving a
    half-created agent sitting in the UI forever.

Happy to supply the full sample series or run any query that would help.

Hey @Nico_Greenarry
This isn’t your usage and it’s not something you misconfigured; it’s an issue we’re tracking. One thing that changes the fix: the extra rows aren’t retained revisions, they’re full transcript copies made whenever an agent self-forks (resume: "self"), which is why it concentrates in a few long chats and why GC Agent KV Blobs only cleared ~18% (those copies are live data).

To stop the regrowth: avoid repeated resume: "self" in a long-running chat. A regular subagent, or a fresh chat, doesn’t copy the parent, so growth stays linear.

To reclaim the rest (it’s live data sitting inside those two or three giant chats, so GC alone won’t touch it):

  1. Export Chat (Command Palette) on anything you want to keep.
  2. Delete those chats, or use Delete Old Chats… by age.
  3. Run GC Agent KV Blobs, then fully quit with Cmd+Q, not a window reload. The -wal only merges back on a full quit, and that quit can look like a multi-minute hang. Let it finish, don’t force-quit.

You’re right about the misleading reload-vs-quit step and the missing progress/warning too. I’ve passed that on. Let me know how it goes.

Same issue on Windows 10, Cursor 3.23.12. In my case it got worse: once state.vscdb reached 51.7 GB, Cursor no longer started. Every launch showed “Cursor was unable to open local storage” with no recovery option.

Read-only analysis of the DB (sqlite3 ?mode=ro):

  • VACUUM would not help. The freelist was only 0.8 MB; all 51.7 GB is live data.

  • Breakdown of cursorDiskKV (48.7 GB of values):

    key prefix rows size
    agentKv:blob 1.45M 20.6 GB
    bubbleId 1.43M 14.1 GB
    checkpointId 52K 10.4 GB
  • Very little of it is actual chat text: about 0.21 GB across roughly 4,600 non-empty conversations.

  • It matches the self-fork duplication described above.

    • 792,620 bubble rows are not listed in their composer’s fullConversationHeadersOnly, and more than 99.9% of them are copies of listed bubbles.
    • One chat has 9,957 listed bubbles but about 130k stored rows.
    • Four identical 45.8 MB bubbles sit in four different composers.
  • Growth jumped to about 1,450 composers a month in Jul–Sep 2026, from about 200 a month before.

Workaround that got me running without losing anything:

  1. Quit Cursor.
  2. Move all four state.vscdb* files together. Never separate -wal from the DB.
  3. Launch Cursor so it creates a fresh DB, then quit.
  4. Copy every ItemTable row (only 3.3 MB) from the old DB into the new one. This restores layout, theme and login.
  5. Export the old chats to markdown from the old DB, opened read-only.
  6. Optional: bring recent chats back into the sidebar.
    • Copy composerData:<id>, only the listed bubbleId rows, and the composerHeaders row.
    • That restores 30 days of chats (191 conversations) in about 0.9 GB instead of 51 GB.
    • To continue those chats, you also need the agentKv:blob closure referenced by conversationState. That added about 2.1 GB.
    • Without the blobs, Cursor shows “Conversation data missing … N missing blobs. Start a new chat to continue.”

Requests:

  • A retention policy or size cap, with a warning long before the DB reaches tens of GB.
  • A recovery path when local storage fails to open, such as “start fresh, keep settings” instead of a dead end.
  • Stop writing full transcript copies on self-fork. Store only the listed bubbles, and deduplicate identical large payloads.
  • A per-chat “archive” action: export the chat to markdown or JSON, then remove its bubbles, checkpoints and exclusive agentKv blobs.
    • “Delete” doesn’t keep a copy.
    • “GC Agent KV Blobs” reportedly needs about 2x the DB size in free disk and reclaims only part of the space.
  • Clearer docs on what “Delete Old Chats” and “GC Agent KV Blobs” actually remove.