Backup and restore#
openprogram backup snapshots the profile state directory — your memory
workspace, sessions, configuration, and channel bindings — into a single
tar.gz, and restores it later.
The problem it solves: everything OpenProgram remembers about you lives in one
hidden directory. A bad migration, an experiment on the memory workspace, or a
mistyped rm takes all of it at once, and none of it is in git.
Quick reference#
openprogram backup create # snapshot now
openprogram backup list # what do I have?
openprogram backup restore <name> --dry-run # what would this overwrite?
openprogram backup restore <name> # do it
openprogram backup prune --keep 5 # delete all but the newest 5
Archives land in ~/.openprogram/backups/ (or
~/.openprogram-<profile>/backups/ under a named profile), named
<profile>-<timestamp>.tar.gz and created with mode 0600 — readable only
by you. The archive is written to a unique owner-only temporary file, flushed,
and atomically published; POSIX also flushes the containing directory.
What gets backed up#
The scope is an allowlist, not "everything except". A new cache directory added by a future release cannot silently start bloating your archives.
| Included | What it holds |
|---|---|
memory/ |
The memory workspace: core.md, topics, timeline, sources |
sessions/, sessions.db, session_aliases.json |
Chat history and its index |
config.json, cli-config.json |
Your configuration |
agents/, agents.json |
Agent definitions |
programs_meta.json, functions_meta.json, program-sources.json |
Program and function metadata |
channels/, bindings.json |
Channel accounts and session bindings |
skills/, skills.json |
The skill registry |
plugins/, marketplaces.json |
Installed plugins |
mcp_servers.json, models/, commands/ |
MCP servers, model overrides, custom commands |
owner.json, projects/, profiles/, worktrees.json, usage.db |
Ownership, project and account metadata, worktrees, usage history |
Left out on purpose, because it is regenerated on next start and would only make the archive bigger:
cache/,tool_results/,browser-states/,chrome-profile/trash/andshadow-git/logs/and any*.log- Locks, PID files, and port files (
*.lock,*.pid,*.port) - The web token, which is regenerated every launch
- Credential-writer temporary files such as
.env.tmpand*.json.tmp - Credential-account
profiles/*/home/trees. Onlymetadata.jsonand the registered.envand AuthStore inventory paths are eligible. node_modules/anywhere in the tree- Symlinks, which are skipped rather than followed — a link out of the state directory would pull unrelated trees into the archive
Credentials#
By default, the archive omits auth/, mcp_tokens/, profile AuthStore files and .env files,
and Channel credentials.json files. It removes config.json[api_keys] and MCP
server env, header, bearer-token, and OAuth client-secret fields while keeping
the rest of those mixed configuration files.
The Web runtime token and pending Channel pairing codes are never archived, including when credential opt-in is enabled.
openprogram backup create --include-credentials
This opts in to every registered persistent credential category and prints an
accurate plaintext warning. Store the resulting file with the same access
restrictions as the original credentials. backup-manifest.json records only
credential categories that were actually included, redacted, or excluded in
that archive. Global never-backup rules are reported separately under
credential_policy, without recording secret values. If you only need to move
to a new machine, logging in again is usually safer than copying the archive.
Restoring#
openprogram backup restore default-20260811-012458.tar.gz
Three things happen before anything is overwritten:
- Running-process check. If a worker or web server is up, the restore is
refused — restoring session files underneath a live process corrupts both.
Stop it first with
openprogram stop. - Confirmation. The command lists what it is about to overwrite and waits
for
y. Pass-yto skip this in a script. - Automatic safety snapshot. Your current state is backed up as
<profile>-pre-restore-<timestamp>.tar.gzfirst, so a mistaken restore is itself undoable. Restoring an archive that was created with--include-credentialsmakes that snapshot include credentials too, under the same authorization — otherwise the undo would drop the very secrets the restore replaced. The command says so when it happens.
Use --dry-run to see the overwrite list without any of this happening:
openprogram backup restore default-20260811-012458.tar.gz --dry-run
Restore only replaces the entries present in the archive. State outside that scope — caches, logs — is left alone. When a mixed file omits or redacts a registered secret field, restore keeps the current machine's value for that field instead of replacing it with a mask or deleting it.
The whole archive is validated before any of it becomes visible: containment, member type, and the JSON shape of every registered secret file. Symlink, hardlink, and path-traversal members are refused outright, and a rejected archive leaves your state exactly as it was. Each file is then published through the same owner-only atomic writer the rest of OpenProgram uses, and every publish is journalled — so a restore interrupted by a crash or a full disk is reversed rather than left half-applied. Recovery runs automatically at the start of the next restore.
Pruning#
Backups are never deleted automatically. When they pile up:
openprogram backup prune --keep 5
This keeps the five newest and deletes the rest, printing how much space it
freed. --keep defaults to 5, and values below 1 are rejected.
Profiles#
Every subcommand operates on the active profile only. Under
--profile alpha, backups are read from and written to
~/.openprogram-alpha/backups/, and a restore can never cross into another
profile's state.