Backup, export and import
Bu içerik henüz dilinizde mevcut değil.
Everything Control Center can copy, hand over or adopt, and the exact shape of each artifact. To actually take a backup, see Back up and restore; for why persistence is split this way, see Workspaces and isolation.
Artifacts
Section titled “Artifacts”There are three database-copy artifacts, with different scopes and deletion policies.
| Artifact | Written by | Shape | Covers |
|---|---|---|---|
| Install snapshot | server.backupNow |
A timestamped directory | Every database on the install |
| Workspace backup | Workspace backup pipeline |
Timestamped directory with one .db |
One workspace’s database |
| Workspace export | workspace.export |
A single .db file |
One workspace’s database |
All are written with SQLite VACUUM INTO, which is safe against a live WAL database:
it takes a read transaction, produces a defragmented and transactionally consistent
copy, and never requires stopping the server.
Snapshot layout
Section titled “Snapshot layout”<dataDir>/backups/2026-07-28T09-12-33-000Z/ manifest.json global.db <workspaceId>/workspace.db one directory per workspaceThe layout mirrors the live data directory exactly, which is what makes restoring an
install a copy back rather than a translation. The directory name is the UTC timestamp
with : and . replaced by -, so it is filesystem-safe on Windows and sorts
chronologically as a plain string.
Scheduled workspace backups live separately at
<dataDir>/backups/workspace-backups/<workspaceId>/<timestamp>/workspace.db.
They do not include a manifest or global.db and are not install snapshots.
The weekly pipeline’s retention step removes only backups of its own workspace
older than its configured age (30 days by default); manually created install
snapshots are never rotated by that step. server.listBackups lists only install
snapshots, including incomplete ones.
manifest.json
Section titled “manifest.json”| Field | Type | Meaning |
|---|---|---|
version |
int | Manifest format version. Currently 1 |
created_at |
ISO-8601 UTC | When the snapshot was taken |
global.file |
string | Always global.db |
global.schema_version |
int | GlobalDatabase schema version at capture time |
global.bytes |
int | Size of the written global.db |
workspace_schema_version |
int | WorkspaceDatabase schema version shared by every workspace file |
workspaces[] |
array | One entry per captured workspace |
workspaces[].workspace_id |
string | The workspace this file holds |
workspaces[].file |
string | Path relative to the snapshot root, <workspaceId>/workspace.db |
workspaces[].bytes |
int | Size of that file |
skipped_workspaces[] |
array of string | Workspaces the backup could not capture |
A workspace is skipped when it is registered but has never been written to (so no file
exists yet), or when its VACUUM INTO failed. Either way the snapshot still completes
and the manifest names what is missing.
Completeness
Section titled “Completeness”server.listBackups reports a complete flag per snapshot. It is false when the
manifest is absent or unparseable, when global.db is missing, or when the manifest
names a workspace file that is not on disk.
An incomplete snapshot is still listed, because a snapshot an operator cannot see is a
snapshot they believe they have. When the manifest is unreadable the listing falls back
to the layout itself and reports whichever <workspaceId>/workspace.db files it finds —
those remain individually adoptable through workspace.import even though the snapshot
as a whole cannot restore an install.
What a backup does not contain
Section titled “What a backup does not contain”Both artifacts are databases only. A workspace directory holds more than its database, and none of the rest travels:
| Path | In a snapshot? | In a workspace export? |
|---|---|---|
global.db |
Yes | No |
<workspaceId>/workspace.db |
Yes | Yes (it is the file) |
<workspaceId>/blobs/ |
No | No |
<workspaceId>/skills/ |
No | No |
<workspaceId>/agents/ |
No | No |
<workspaceId>/chat_credentials/ |
No | No |
<workspaceId>/spaces/<spaceId>/ |
No | No |
<workspaceId>/pr_clones/ |
No | No |
secrets.json |
No | No |
models/ |
No | No |
meetings/<meetingId>/ |
No | No |
rigs/ |
No | No |
Some of that is disposable — spaces/ holds copy-on-write worktrees that are
re-provisioned from the repos, pr_clones/ is a cache, models/ re-downloads, rigs/
is machine state. Some of it is not: blobs/ holds pasted images that rows in the
database point at, skills/ and agents/ hold the files an agent runs with, and
secrets.json holds every pairing key, provider credential and SSO setting on the
install. See Back up and restore
for covering the rest.
Operations
Section titled “Operations”Install backup RPCs and workspace export/import are fullClient-only: a paired
phone cannot trigger them. All are absent entirely — not refused, structurally
missing — on a server with no backup port wired, such as the demo server.
| Op | Kind | Scope | Minimum role | Arguments | Returns |
|---|---|---|---|---|---|
server.backupNow |
mutate | Install | install owner | none | {ok, path} |
server.listBackups |
read | Install | install owner | none | {backups: [...]} |
server.deleteBackup |
mutate | Install | install owner | name |
{ok} |
workspace.export |
read | Workspace | admin |
none | {ok, path} |
workspace.import |
mutate | Workspace | owner |
source_path |
{ok, workspace_id} |
workspace.delete |
mutate | Install* | owner |
id |
registry row marked deleted |
workspace.deleteis unscoped because the row it marks lives in the global registry; its handler checks the caller’s membership of the named workspace and requiresowner.
path in every response is a path on the server host.
server.listBackups response
Section titled “server.listBackups response”Newest first, sorted by directory name (which is the timestamp, so the order holds even for a snapshot whose manifest could not be read).
{ "backups": [ { "path": "/…/backups/2026-07-28T09-12-33-000Z", "name": "2026-07-28T09-12-33-000Z", "created_at": "2026-07-28T09:12:33.000Z", "bytes": 481203712, "complete": true, "workspaces": [ { "workspace_id": "w_9f2c…", "path": "/…/backups/2026-07-28T09-12-33-000Z/w_9f2c…/workspace.db", "bytes": 190840832 } ], "skipped_workspace_ids": [] } ]}created_at is omitted when the manifest could not be read. bytes at the top level is
the real size on disk of the whole directory, not the manifest’s own sum — the question
an operator pruning snapshots is asking is what the directory costs, not what the backup
meant to write.
Each workspaces[].path is exactly what workspace.import takes as source_path. That
is deliberate: “restore this workspace from this snapshot” is the import op, so the two
cannot drift into different treatments of the same file.
workspace.import validation
Section titled “workspace.import validation”Before anything is replaced, the candidate file is opened read-only with a short-lived
raw SQLite connection and its workspace_meta row is read.
| Condition | Result |
|---|---|
Not a readable SQLite database, or no workspace_meta |
Refused. Nothing is touched |
| Recorded workspace id ≠ the target | Allowed, logged. This is how a workspace is duplicated or restored under a new id |
| Recorded install id ≠ this install | Allowed, logged. Its paired devices and user ids belong to that install and will not resolve here |
| Invalid workspace id, or file does not exist | Refused |
On acceptance the target workspace’s database is closed and dropped from the manager
before the file is copied over it (an open drift connection holds the file and its
-wal), then reopened so beforeOpen reinstalls the FTS and sync triggers, and the FTS
indexes are rebuilt — an imported file’s content tables are populated but its index may
be stale.
Transfer routes
Section titled “Transfer routes”Every op above speaks in server-side paths, which is a complete answer only when the
server is your own machine. These three carry the bytes, on the same PSK-signed lane the
media proxy and blob store use. The caller signs a target string with its device
pre-shared key and passes the signature as s.
| Route | Method | Query | Signed target | Requires |
|---|---|---|---|---|
/backup/workspace |
GET | w, d, s |
backup-workspace:<workspaceId> |
Workspace admin |
/backup/snapshot |
GET | n, d, s |
backup-snapshot:<name> |
Install owner |
/backup/restore |
POST | w, d, s |
backup-restore:<workspaceId> |
Workspace owner |
w is a workspace id, n a snapshot directory name, d the calling device id, s the
signature. n is resolved against the backup listing rather than joined onto the backups
root, so the route cannot be aimed at a directory the listing would not have shown.
| Status | Cause |
|---|---|
400 |
A required query parameter is missing, or an upload body is empty |
403 |
Unknown or revoked device, bad signature, or the caller lacks the role |
404 |
No backup port on this host, or the named snapshot/workspace file does not exist |
405 |
/backup/restore called with anything but POST |
413 |
Upload body exceeds 8 GiB, by declared content-length or by bytes actually read |
500 |
The export or archive failed server-side |
Both downloads respond Cache-Control: no-store with Accept-Ranges: none. Each request
mints a fresh copy and deletes it once streamed, so a cached or resumed response
would splice bytes from two different exports into one file that looks valid and is not.
/backup/snapshot returns <name>.zip, entries relative to the snapshot root so
unpacking reproduces manifest.json + global.db + <workspaceId>/workspace.db.
Entries are written in sorted order, so two archives of the same snapshot are the same
archive.
/backup/restore streams the body straight to a staging file rather than buffering it,
bounds it before and during the read, and deletes the staged file on every path out
including a refusal.
Paths and limits
Section titled “Paths and limits”| Thing | Value |
|---|---|
| Snapshots | <dataDir>/backups/<timestamp>/ |
| Scheduled workspace backups | <dataDir>/backups/workspace-backups/<workspaceId>/<timestamp>/workspace.db |
| Workspace exports | <dataDir>/backups/exports/<workspaceId>-<timestamp>.db |
| Transfer staging | <dataDir>/backups/transfer/ — a workbench, swept by the route that writes it |
| Snapshot archive name | <snapshot directory name>.zip |
| Upload ceiling | 8 GiB per POST /backup/restore |
| Retention | Install snapshots: manual deletion. Workspace backups: weekly pipeline, 30-day default |
The exports/, transfer/, and workspace-backups/ directories are not
install snapshots and are excluded from server.listBackups. Only the last
directory has scheduled retention; deleting an install snapshot requires the
install owner’s explicit action.
See also
Section titled “See also”- Back up and restore — the recipes these operations belong to
- Workspaces and isolation — why one workspace is one file
cc_serverCLI — the data directory these paths sit in- Route map — where the UI lives
- Architecture — the client/server split the paths follow