Where state lives, and how to back it up
Everything VCode remembers is plain files under one directory,
VCODE_STATE. Copy it to back up; delete it to start clean. There is no
database.
Why you would use it
You are moving VCode to another machine, want a backup before an upgrade, or want the disk space back from a year of transcripts.
How to use it
- Find the root. Under the unit it is
~/.local/state/v-code/workbench/threads(theworkbench/directory keeps its name from before the rename); for a barenode server.jsit is~/.v-code/threads. The startup line names it:state <path>. - Back up:
tar czf v-code-state.tgz -C ~/.local/state/v-code/workbench threads. Stopping the unit first gives a clean snapshot, but the logs are append-only, so a copy taken while it runs loses at most a torn trailing line. - Restore: stop the unit, unpack in place, start it.
- Start clean: stop the unit,
rm -rfthe directory, start it. VCode recreates it empty. - Delete one thread instead: close the tab, then delete it from the UI. That is a recursive remove of the thread's own directory.
What you see
<VCODE_STATE>/
settings.json the cog's settings
system-prompt.md your system prompt, added after VCode's own (0600)
settings-sync.json what the account's settings copy last held (0600)
tab-order.json the tab strip's order
projects.json the projects: name, folders, last agent (0600)
vapid.json push keys (0600)
push-subscriptions.json subscribed devices (0600)
<thread-id>/
events.ndjson the canonical event log, one JSON object per line
raw.ndjson verbatim vendor frames
raw.1.ndjson the previous raw file, after a rotation
attachments/ files uploaded into that thread
system-prompt.md an opencode thread's prompt, rewritten at each start (0600)
The thread directory name is the thread id put through encodeURIComponent.
Options and settings
| Option | Default | What it changes |
|---|---|---|
VCODE_STATE |
~/.v-code/threads; $STATE_DIR/workbench/threads in the unit |
The root of everything above |
--state-dir DIR (install.sh) |
~/.local/state/v-code |
The $STATE_DIR the unit's paths are built from |
| Memory tail | 2000 events per thread | How many events stay resident; older ones are re-read from the file |
| Raw rotation | 25 MB | When raw.ndjson becomes raw.1.ndjson. Two files are kept |
Limits and known gaps
- Never rotate
vapid.json. Every installed device's push subscription is bound to that keypair. Losing it means every device has to subscribe again. Back it up with the rest. - Deleting a thread is a hard delete: the whole directory, attachments included, goes. The route answers 409 unless the thread is closed first.
- Nothing here is encrypted. The transcripts hold whatever your agents read and
wrote, and the attachments hold whatever you uploaded.
vapid.jsonandpush-subscriptions.json,system-prompt.md(both the root's and a thread's),settings-sync.jsonandprojects.jsonare written 0600; the rest takes the umask. - A torn trailing write (power loss,
kill -9) is survivable: everything after the first unparseable line is discarded on read, so the log never poisons a boot. raw.ndjsonis a re-derivation source, not a replay source. Once it has rotated twice, the oldest raw frames are gone.events.ndjsonis never truncated.- Moving the state root between machines carries the threads, but not the vendor
sessions they resume from (
~/.claude/projects,~/.codex/sessions).
Related
- install-as-systemd-units: how
$STATE_DIRis chosen - environment-variables:
VCODE_STATE - memory-limits-and-parking: the other resource ceiling
- system-prompt: what
system-prompt.mddoes - synced-settings: what
settings-sync.jsontracks - turn-on-notifications: the keys in
vapid.json - close-and-reopen-tabs: closing a thread before it can be deleted
- attach-photos-and-files: what lands in
attachments/ - reorder-tabs: what
tab-order.jsonholds