Safe-by-default writes¶
Writes are preview-first. Every command that changes your Zotero library or your vault previews by default, applies only under --yes, and counts what it plans against --max-changes. That covers the bulk fixers and importers (items enrich, tags audit fix, items duplicates resolve, import apply, import doi, import file, import url, and the generic import <resource>), every single-item create (items new, import pmid, import arxiv, import isbn), the single-resource CRUD commands (items create/update/delete/restore, collections create/update/delete), and every vault command (vault push, vault pull, vault sync, vault resolve); workflow run extends the same contract across a multi-step plan. Read the contract once and you never have to guess which command is dangerous.
The contract¶
- Preview is the default. You get a plan/result envelope with zero changes.
--yesapplies;--dry-runalways wins. A preview runs the same reads as an apply, so it reports real no-ops and real versions, and it evaluates the same gates (--max-changes,--allow-destructive, input validation). The→ writing via Zotero Web APInotice and any config save happen only on apply. --dry-runmeans no write of any kind. It covers Zotero writes and also local files (including.locksiblings), config saves, journal entries, and mirror write-through. Read commands run normally under--dry-run.--deliveris output routing.--deliver file:<path>orwebhook:<url>sends the command's own output to a sink, like shell redirection.--dry-rundoes not suppress it.--agentdoes not auto-apply. Agent mode sets JSON + non-interactive defaults, but a write still needs an explicit--yes.- Every Zotero write is gated. Every command that writes to Zotero or to the vault needs
--yes, and--dry-runoverrides it. Aworkflow runpreview injects--dry-runinto each step that supports it. Steps that cannot preview, such assync, are skipped with the reasonnot previewable; runs only on apply. Commands that destroy local state (auth logout,profile delete) also preview by default and apply with--yes. Commands that create local state (auth set-token,profile save) apply directly. The vault commands keep their own per-note report rather than the generic envelope, but they obey the same gate. - Gates cap the blast radius.
--max-changesdefaults to 500 (50 under--agent); irreversible ops (merge, permanent delete, collection delete,creators rename,creators audit fix,vault resolve) refuse to run without--allow-destructive. - Read-your-writes. A write applied through the mutation envelope is replayed into the local mirror immediately and the post-write item state comes back in the envelope, so a re-audit sees the fix with no follow-up
sync. Replay is deliberately conservative: scalar field edits, tag/collection membership, item creates, trash, restores, and permanent deletes are replayed when the mirror can express them, while unsupported structural edits are left for the nextsyncto reconcile authoritatively. A mirror update that itself fails is reported as a warning without failing the already-applied write. - Journaled, and honest about undo. Runs applied through the mutation envelope are recorded append-only (
journal list/journal show), including the single-resource CRUD commands and partially-rejected batches (a 100-item create where one element is refused still journals the 99 that landed). Some gated writes deliberately sit outside the envelope and so are not journaled: thevaultcommands, which keep their own report.journal undo <run-id>reverses only what it can invert losslessly — tag and collection membership toggles, creates (reversed by trashing the created item), anditems enrichwrites of a DOI, abstract, citation fields, or subject and MeSH tags — and loudly refuses everything else (merges, deletions, other field overwrites) rather than guessing. An enrich run records its own inverse while it writes: the value each field held, the tags it added, and the exactzotio:provenance line it appended to Extra. Undo takes that line out of the live Extra and leaves every other line alone; it restores a field only while the field still holds the value enrich wrote; and it still works when the line is already gone. A run journaled before enrich recorded this keeps its old behaviour: its tags undo, its Extra line stays, and its field overwrites are refused. A journal entry is therefore an audit record, not a promise of reversibility:items deleteis journaled but not undoable, because the journal records what a run did, not a full pre-image of what it replaced. Recover a deleted item from Zotero's trash withitems restore. - One writer at a time. Applying a write takes the installation-wide advisory lock (
~/.zotio/.writer.lock).export snapshotandcollections bundleinstead lock their canonical output path, so runs writing to different directories stay parallel, and a vault write holds both the installation lock and its vault-path lock. Writers do not queue: a second concurrent writer in the same scope exits 9 immediately and is safe to retry once the first finishes. Reads and previews are never blocked, so parallelize those and serialize applies. See Architecture decisions › Single-writer concurrency. - Partial success is loud. Zotero answers a batched write with HTTP 200 even when it rejected some elements. Those rejections are reported with their source index and message, and the command exits 13 (degraded) — never 0.
Across a whole workflow¶
The contract scales from one command to a plan. zotio workflow run previews every mutating step by default and applies the whole plan on a single --yes; every mutation in the run shares one journal run ID (journal list --workflow <id>), and an interrupted run resumes from a checkpoint without replaying a write whose outcome is uncertain. See Workflows & triggers.
Where writes land¶
Writes split by intent: new personal-library items prefer the keyless local desktop connector. Edits and note write-back need a Web API key. attachments add --via connector saves bytes through the desktop but still needs a Web API key to move the attachment to an existing item.
The capabilities reference lists the operation, write target, destructiveness, and requirements for every command. See Authentication for key setup.