✨ feat(sync): inspect and repair the local pCloud sync daemon - #1
Merged
Merged
Conversation
- Add `sync status` to inspect local sync pairs and report health issues - Add `sync prune` to remove orphaned sync pairs and their local indices - Implement database snapshot reading that bypasses WAL locks - Add comprehensive tests and documentation for sync operations - Detect orphaned, remote-missing, and stuck sync issues with visual indicators
- Add `npm test` step to `.github/workflows/ci.yml`, between typecheck and build - Ensures test failures block CI before a build is attempted, closing a gap where only typecheck and build were gated
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Every command in this CLI talks to the pCloud API. None of them can see the
local database the pCloud Drive desktop app keeps at
~/.pcloud/data.db,which is where sync pairs actually live — so a broken sync pair is invisible to
pcloud doctorand to every other command here.This was found the hard way. A sync pair had lost its remote folder:
syncfolder.folderidis declaredON DELETE SET NULL, so when the remote folderwas deleted, SQLite blanked the reference rather than removing the pair. The pair
survived as a zombie pointing at nothing, alongside a healthy duplicate for the
same local folder. pCloud Drive rendered the null remote as
/and reported thefailure as:
Which is neither a permissions problem nor a folder named
/. Diagnosing it tookmanual SQLite spelunking; this PR makes it one command.
What
pcloud syncpcloud sync <id>pcloud sync --jsonpcloud sync --debugpcloud sync prune <id>pcloud doctorFive checks, each derived from a real failure mode in the schema:
orphaned—syncfolder.folderid IS NULL; the fault aboveremote-missing—folderidno longer present in thefoldertableduplicate— two pairs claiming the same local pathlocal-missing— the local folder is gone from diskstuck—taskrows withitemid = 0, i.e. queued with nowhere to goNotes for review
Foreign keys are explicitly disabled.
node:sqliteenables them by defaultwhere pCloud's own writer does not. Enforcing constraints we did not author would
reject the broken rows these checks exist to find — a dangling
folderidis thebug, so a connection that refuses to read one is useless. See the comment on
OPENinsrc/lib/sync.ts.All reads go through a snapshot. The daemon holds the database under an
exclusive WAL lock while it runs, so opening it in place fails outright rather
than degrading to a stale read.
snapshot()copiesdata.dbplus its-waland-shmto a temp dir and opens the copy; the WAL files are absent after a cleancheckpoint, which is treated as valid rather than as a broken database.
The debug view uses an allowlist, never an exclusion list. The same database
holds
setting,cryptofilekeyandcryptofolderkey— auth token and cryptokey material. A deny-list would leak the first sensitive table pCloud adds in a
future release. Withheld tables are named, never read. There is a test asserting
all three are absent from
DEBUG_TABLES.prune --applyis gated three ways — dry run by default, backs updata.dbbefore writing, and refuses to run while pCloud Drive is up (checked via both
process list and
lsof, since the process may have exited while still holdingthe file). It deletes child-first inside a transaction and touches no files on
disk or in the cloud.
doctor's local section runs unauthenticated. It needs no credential, and abroken sync pair is exactly the fault someone hits before getting round to
logging in — so only the remote half is given up on.
Testing
npm test— 17 tests, all passing.npm run typecheckclean.The tests build their fixture from
CREATE TABLErather than snapshotting a realdatabase. That is deliberate: copying a live WAL mid-write yields a torn log whose
recovered contents differ between opens, so a scraped fixture produces failures
that mean nothing. Vitest is added as the test runner — this repo previously had
none.
Verified manually against a real database containing the orphan, exercising every
surface: the table, single-pair detail,
--json,--debug, the prune dry run,and the running-daemon guard (refused correctly, exit 1).
Follow-ups
src/lib/sync.ts, mirroring howsrc/lib/health.tssits beside
doctor. If an MCP or Raycast surface ever wants local sync state,it should move into
@kud/pcloudrather than being duplicated.pcloud doctorremains undocumented in the README anddocs/— pre-existingdrift, deliberately left alone here.
pgrep/lsofagainst the.appbundle path.