| Cannot create a store in data/: the directory is not empty, PersistError::DirectoryNotEmpty | a new store (Store::create, the target of Store::convert) needs a new or empty directory | choose an empty or new directory, or open the existing store if it is one (graphersal store info data/) |

FAQ and Troubleshooting

Choosing

Snapshot, journal or Store? A .gsnap when the application decides when to save (a script, the browser, a download). A journal when you need durability over streams you manage yourself. The Store for everything else: it is a snapshot plus a journal plus recovery, retention, backups and repair. See Persistence.

Directory or single file? A directory for a server (parts can live on other disks, the files are easy to inspect); a .gstore file when the store travels as one document. Both have the same guarantees, and graphersal store convert switches between them.

Is GraphSON or GraphML not enough? They are exchange formats: they do not carry the schema, the commit position or the auto-id sequences, and GraphML loses value types without a schema. A snapshot is lossless.

Errors and what to do

MessageCauseWhat to do
The store data/ is in use: another writer holds its locka dev server, a REPL or a program has the store open read-writeuse the reading commands (they work), the dev server's Store menu, or stop the writer. A crashed writer never leaves a stale lock (the OS releases it)
data/ is not a Graphersal store: no valid GRAPH file (...)the path is not a store (a typo, or not created yet), or both GRAPH copies are unreadablecheck the path; graphersal --graph <dir> --create-store creates a store; for damage see Damage
... is a BACKUP (up to commit N, ...), PersistError::IsBackupa backup opens read-onlygraphersal store restore <dir> makes it the live store, store fork <dir> <new> a writable copy
warning: the store ... has DAMAGE and opened READ-ONLY in maintenance modechecksums failed, a WAL segment is missing, ...follow the runbook: stop writing, verify, repair --to
The commit was rejected by the commit hook 'maintenance'a write to a store in maintenance modethe same: repair into a new directory
The journal is poisoned by an earlier failurean fsync or a write failed (a full disk, an I/O error)fix the cause, then reopen the store (or restart the server): recovery continues from the last durable commit
The journal is poisoned by an earlier failure: a panic while writing a commit record: ...the journal panicked while it wrote a record (a bug, or a store backend that panics); the record was cut off again and the unit rolled backreport the panic; reopen the store (or restart the server): it gives the state before that unit. Every case: Failures while committing
Commit N is too large for the journal: its uncompressed record body is B bytes, more than the limit of 1073741824 bytes per commit / CommitTooLarge (in CommitRejected from the hook journal)one unit changed more than 1 GiB of WAL record (every change with its before and after values: dropping and re-creating a large graph in one script of the dev server or the playground)split the work: drop in a run of its own, load in several runs; see The size of one commit. The unit rolled back; the store keeps working
Recovery target mark "x" not reachedno such mark, a commit after the end, or a target before the oldest retained snapshot (pruned)store marks, store list, store info show what exists
Cannot roll the store back: ... there is nothing after commit Nthe target is the current commit or laterchoose an earlier target
Cannot <operation>: <reason> / PersistError::Storethe store's state refuses the operation; its cause (StoreFailure, Python err.cause) says why, see the table belowfollow the help printed with it
--at-time: "..." is not a date-time: add a zonea time without a zone, or a bare datewrite 2026-10-08T02:43:00Z (UTC) or an offset like +02:00
Incremental backup ... refused / BackupRefusedanother graph, a pruned gap, a rollback behind the backup, a non-empty directoryback up into a new directory with --full
The backup stopped: the store ... is damaged: <file> at byte N: ... / BackupDamaged, side Storea checksum of something the backup copies failed in the storerepair the STORE (graphersal store repair <dir> --to <new_dir>), never the backup; while the store is open in a server or a program, save its graph first with a backup from memory. Nothing of the failed backup was kept
The backup stopped: the backup ... does not verify / side Backupthe copy did not read back intact (the backup's disk)check the backup's disk and back up again (into a new directory with --full when the old backup is damaged); the store is fine
The store ... is read-only: backup found damage on disk ... damage policy "maintenance" / DamageFounddamage was found while the store is open, and its policy (fixed at creation) is maintenancethe data in memory is intact: back it up from memory, then repair the store; see Damage found while the store is open
--on-damage ... applies only to a store being created--on-damage for an existing store with another policythe damage policy never changes; drop the option
The store ... has the older format version N; this build reads and writes only version M / OlderFormata store written by an older Graphersal (store format version 1: before the store id)migrate it through a packed snapshot: the recipe, also in the error's help
Unsupported format version N of the store ... / UnsupportedVersiona store written by a NEWER Graphersalopen it with that version; see Format Versions
The snapshot needs about N bytes in memory, more than the memory limita memory budget (--memory-limit, ReadOptions::with_memory_limit) smaller than the graphraise the budget, or load on a bigger machine

Refused store operations (PersistError::Store)

A refused operation reads Cannot <operation>: <reason>. Its cause is one of a closed set (graphersal::persist::StoreFailure, #[non_exhaustive]), so a program or a server can react to it with a match; StoreFailure::name() is the stable name, which the Python exception carries as err.cause (None for every other error). Each cause has its own help:

Cause (name())WhenWhat to do
Closed (closed)the store was closed (close, shutdown)open it again
LockPoisoned (lock_poisoned)a thread panicked while it held a lock of the open storeend the process and reopen the store
NameTaken (name_taken)a snapshot name in use, or a mark name used before (also by a mark a prune made unreachable)choose another name; store list, store marks
NameTooLong (name_too_long)a snapshot name over 255 bytesa shorter name
SnapshotExists (snapshot_exists)a named checkpoint at a commit that already has a snapshot under another namecommit something first, or checkpoint without a name
NoSnapshot (no_snapshot)an export of a commit without a snapshot, a backup of a store without onestore list, store checkpoint
BackupRunning (backup_running)a prune while a backup copies the filesprune again afterwards
PruneRunning (prune_running)a backup while a prune deletes filesback up again afterwards
NothingAfterTarget (nothing_after_target)a rollback to the current commit or lateran earlier target
UnknownAtticEntry (unknown_attic_entry)no attic entry with that idstore attic <dir> lists them
AtticLineageChanged (attic_lineage_changed)a later rollback or restore came after the entry's rollbackrestore the newer entry first, or fork the entry
ChangedSinceRollback (changed_since_rollback)commits or marks since the entry's rollbackfork the entry (store attic <dir> fork <id> <new_dir>)
AtticBaseMissing (attic_base_missing)the snapshot an attic entry continues is gonestore verify; take the entry from a backup or remove it
NotABackup (not_a_backup)a backup-only operation (prune_backup) on a live storeopen and prune it as a store
KeptSnapshotDamaged (kept_snapshot_damaged)a snapshot a prune would keep fails verification; nothing was removedstore verify, then store repair <dir> --to <new_dir>
CopyDoesNotVerify (copy_does_not_verify)a converted or repaired copy does not read back intactcheck the target disk, repeat into a new directory
ChangedConcurrently (changed_concurrently)a store file changed or vanished while the operation read itnever edit a store's files; repeat, then store verify
JournalSyncFailed (journal_sync_failed)writing or syncing the journal failedfix the disk, reopen the store
MissingCapability (missing_capability)the graph's storage does not claim atomic and change_capture, so no journal can be attached (never for TraversalGraph)keep the graph in a storage that claims both
ReloadFailed (reload_failed)the files of a rollback or attic restore changed, but loading the new state into the open graph failed: the graph in memory was emptied and the store stoppedclose the store and open it again (restart the server): the open completes the change
Stopped (stopped)an earlier rollback or attic restore failed part-way, so the store writes nothing more (Store::stopped_reason)close the store and open it again

Questions

Can I change the damage policy (or the chunk size) of a store? No. Both are fixed at creation and carried by every fork, repair, conversion and backup. To get another policy, create a new store with it and load the data (for example graphersal store export old/ graph.gsnap, then graphersal store create new/ --from graph.gsnap --on-damage continue).

Does a backup check what it copies? Yes, every checksum, in the store before anything is written and again in the copy. A backup never copies damage silently; see Every checksum, twice.

Is a commit really on disk when the query returns? Yes, with the default Durability::EveryCommit: the WAL record is written and fsynced first; a failed sync rolls the commit back. With Interval or Os a machine crash may lose the last commits (never one in the middle). See Commits and Durability.

The dev server (or my program) was killed. Is the store damaged? No. The next open replays the WAL and cuts an interrupted last write; stderr notes that the store was not closed cleanly, which is harmless. Ctrl+C closes it cleanly in the first place.

Why does the store keep growing? Nothing is deleted implicitly: every commit is in the WAL and every checkpoint adds a snapshot until you prune. store compact-advice tells you what a prune and a compaction would give back.

How do I see the graph as it was yesterday? Store::open_read_only(dir, RecoveryTarget::Time(..)), the dev server's Go to time..., or graphersal store fork data/ yesterday/ --at-time 2026-10-07T18:00:00Z and open the fork. See Point in Time.

I rolled back by mistake. graphersal store attic data/ lists the rolled-back history; store attic data/ restore <ID> undoes the rollback as long as nothing was committed or marked since, otherwise store attic data/ fork <ID> <NEW_DIR> keeps it as a new store. A rollback with --delete cannot be undone (restore a backup).

Can I copy a store with cp or rsync? While no process has it open, yes: no file records a path. While it is open, use graphersal store backup (it copies up to the durable end and pins the files against a concurrent prune); a plain copy of a live store may catch a half-written file.

Can two processes use one store? One writer at a time (an operating-system lock). Any number of readers (info, verify, fork, backup, Store::open_read_only) at the same time as the writer.

Does a backup include the attic? No. It holds the snapshots and the WAL up to the durable end, and the marks.

Where is the time zone? Everything is stored and printed in UTC. --at-time takes any offset, but refuses a time without one.

Does it work in the browser? Snapshots, journals and recovery work over any stream in WebAssembly; the Store works there over a MemDir (the playground does that). There is no disk: download a .gsnap to keep a state.

Can another program read the WAL (change data capture)? Yes. The byte format is public (specification); a reader tails the WAL segments, reads complete records only, and treats records after the writer's durable end as possibly not final. In Rust, store.changes_since(commit) yields the ChangeSets.

Is the format stable? Store format version 2 (with file format version 1) is specified on the specification page; a newer version is refused, never guessed at, and a store of an older version is refused with a migration recipe (a packed snapshot exported by the older build). Before Graphersal 0.1.0 the format may still change.