Skip to content

Control CLI reference

proton-sync is a thin control client. It sends requests to the running daemon over its Unix socket and prints the reply. It runs no sync logic of its own. The daemon answers control requests from an in-memory snapshot on a dedicated task, so every command below responds immediately — even while a sync pass is running.

Terminal window
proton-sync [--socket-path <PATH>] [--json] <command>

When --socket-path is omitted, the CLI uses the same default as the daemon — $XDG_RUNTIME_DIR/proton-sync.sock, with an OS-temp-directory fallback — so on a normal setup you don’t need to pass it.

CommandWhat it does
statusShow what the daemon is doing right now (see below).
historyShow the recent sync history, newest first.
pausePause automatic and manual sync until resumed.
resumeResume sync work.
syncnowTrigger a sync and watch it finish (--no-wait to just schedule it).
stopAsk the running daemon to exit gracefully.
pendingList deletions currently withheld by the delete-approval guard.
approve <path> | approve --allApprove a withheld deletion (or all) so it applies next sync.
deny <path> | deny --allRevoke a prior approval before it applies.

Human-readable by default, --json for scripts

Section titled “Human-readable by default, --json for scripts”

Commands print a concise, git-style summary by default:

$ proton-sync status
● syncing — 2 uploads, 1 download planned
folders ~/ProtonDrive ⇄ /Drive/RemoteFolder
activity downloading Documents/takeout.tgz — 1.4 GiB so far · 3m12s [step 812/6377]
last sync 2m ago
changes 3 queued locally
$ proton-sync history
just now ✓ sync completed — 2 uploads, 1 download
15m ago ✗ sync failed — proton-drive: request failed …

The headline dot reflects the daemon state: idle (everything up to date), syncing (a pass is in flight, with the plan when known), running (changes queued for the next pass), paused, or error (the last pass failed; the error is shown on its own line). Extra lines — queued changes, deletions awaiting approval, the last error — appear only when they apply.

While a pass is in flight, the activity line shows what the daemon is doing right now: which remote folder a full-tree walk is listing (with a running count), which local file the scan is hashing, or which file is transferring — with live bytes-so-far for downloads, sampled from the staging directory while the transfer runs. The same line animates in place on the syncnow spinner.

Pass --json to any command to get the daemon’s raw response instead: status --json prints the full response object (below), history --json just the history array, and pending --json the pending-deletions array. --json output is always the daemon’s data verbatim — so history --json keeps the stored oldest-first order, while the human-readable history renders newest first. Scripts that consumed the old JSON output should add the flag.

syncnow schedules a pass and is acknowledged immediately — the daemon never blocks other control clients while syncing. The CLI then polls status and shows a live spinner until your pass finishes, printing its outcome:

$ proton-sync syncnow
✓ sync completed — 3 uploads

The exit code is non-zero if the watched pass failed. Use --no-wait to only schedule the sync and return at once, and --json to print the final status object (or, with --no-wait, the acknowledgement). While the daemon is paused, syncnow reports the skip and schedules nothing.

{
"status": "running",
"paused": false,
"syncing": false,
"reconcile_seq": 4,
"pending_changes": 0,
"message": "daemon status",
"last_sync_epoch_secs": null,
"last_error": null,
"last_plan_summary": null,
"last_successful_sync_summary": null,
"status_history": [],
"pending_deletions": []
}
  • status / paused / pending_changes — the live daemon state (running, syncing, or paused) and the number of changes waiting to reconcile. The string reports live activity: it stays syncing while a pass is in flight even if a pause was just accepted mid-pass (paused carries the standing request; the CLI shows this combination as “pausing”).
  • syncingtrue while a reconcile pass is actually in flight.
  • reconcile_seq — count of completed passes since the daemon started; clients that scheduled a sync poll until it advances to know their pass finished.
  • last_sync_epoch_secs — when the last sync ran (null before the first).
  • last_error — the most recent error, if any.
  • last_plan_summary / last_successful_sync_summary — nullable plan summaries (upload/download/conflict/… counts).
  • status_history — the recent history array (also available via history).
  • pending_deletions — the items awaiting delete approval (also via pending).
  • activity — what the daemon is doing right now (null when idle): { phase, detail, folders_listed, files_scanned, action_index, action_total, transfer, since_epoch_secs }, where phase is one of scanning-local, listing-remote, fetching-events, executing, committing, and transfer (when a file is moving) carries direction, path, bytes_done (downloads: sampled live from the staging directory), bytes_total (uploads only — the remote listing exposes no size), and started_epoch_secs. Every field is display-only and best-effort; new phases may appear, so render unknown tokens rather than failing.

Each status_history entry has epoch_secs, message, last_error, plan_summary, and successful_sync_summary. The daemon keeps the most recent 20 entries in a JSON file next to the index and reloads them on restart, so recent failures and successes stay visible through status and history. For older history, read the journal:

Terminal window
journalctl --user -u proton-syncd.service

The pending, approve, and deny commands drive the delete-approval guard. pending labels each item with its direction — a LOCAL DELETE (removed on Proton Drive; approving removes your local copy) or a REMOTE DELETE (removed locally; approving removes it on Proton Drive) — so you always know which way a deletion runs before approving.

Terminal window
proton-sync pending
proton-sync approve notes/old.txt # one item
proton-sync approve --all # everything pending
proton-sync deny notes/old.txt # revoke before it applies
proton-sync syncnow # apply now

approve/deny require either a <path> or --all, never both and never neither — a bare approve won’t silently approve everything.

proton-sync stop asks the daemon to exit through the same clean path as a SIGTERM: an in-flight transfer is cancelled (actions that fully completed before the stop keep their checkpoint commits; the interrupted action is never recorded and replans, along with the rest, on next start), the control socket is removed, and the process exits. Under systemd, start it again with systemctl --user start proton-syncd; the unit’s Restart=on-failure does not respawn a clean stop.

The CLI exits 0 on a successful round-trip and non-zero on failure — it can’t reach the daemon, or the pass watched by syncnow failed. If it can’t connect, confirm the daemon is running and that both sides use the same socket path — see Troubleshooting.