fix(aw-sync): skip duplicate device_id folders that truncate history on pull - #686
Conversation
…on pull When one device_id appears under two hostname folders, pull_all imported both. Provenance is the bucket hostname, so they share a destination bucket and resume-from-newest then dropped the older history. Group remotes by device_id and keep the largest db. Warn when a resume-from-newest pull leaves the destination with fewer events than the source. Git-Session-Id: ede65257-42c4-5386-a084-23031fc09a31
|
Heads-up on merge order: this PR and #685 conflict in The overlap is in the design, not just the text:
Suggested order: merge #685 first, since it is the smaller layout fix and CI is green. Then rebase this PR so |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #686 +/- ##
===========================================
+ Coverage 70.81% 81.31% +10.49%
===========================================
Files 51 71 +20
Lines 2916 6460 +3544
===========================================
+ Hits 2065 5253 +3188
- Misses 851 1207 +356 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
CI-green and mergeable — waiting only on a maintainer click. This PR is ready to merge, but the bot has pull-only access to this repo and can't self-merge — surfacing it here so it isn't lost. The monitoring loop will stop re-flagging it now that this note is posted. |
|
@TimeToBuildBob This is the strongest of the three, and I think it should merge first rather than after #685.
The Two smaller notes:
|
list_remote_dbs is the pull_all walker and does not see leftover
{device_id}/test.db files at the sync root. That is intentional:
the ActivityWatch#682 root orphan must not be imported by a peer. Document that
find_remotes stays 2-level, and add a regression test.
ActivityWatch#686
Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
Rebased onto ActivityWatch#686 so the daemon switch and pull-only staging fix sit on RemoteDb / list_remote_dbs instead of a second walker. The default daemon (no --start-date/--buckets/--sync-db) now uses pull_all+push, the same 3-level layout as `aw-sync sync` and Android. list_remote_dbs stays 3-level-only: leftover `{device_id}/test.db` at the sync root (the ActivityWatch#682 orphan) is not a pull candidate. A test locks that so it does not get "fixed" later. The advanced sync_run path is unchanged. Fixes ActivityWatch#682 Git-Session-Id: d963a5fd-8243-53d2-b4e5-7d020965e48b
The default `aw-sync`/`aw-sync daemon` path drove `sync_run` against the
sync root, writing `{device_id}/test.db` and scanning two levels — so it
never saw `{hostname}/{device_id}/*.db` remotes from Android or
`aw-sync sync`. Route the default daemon through `sync_wrapper` like
the simple `sync` command, and only stage a local db on push.
Walker rewrite dropped: `pull_all` already uses `list_remote_dbs`
(3-level-only) after ActivityWatch#686. Broadening
`find_remotes` at the sync root would import the ActivityWatch#682 orphan.
Fixes ActivityWatch#682
Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
`aw-sync daemon` can walk a configured sync dir and stay silent when it finds nothing to pull. This does not change which remotes are pulled (ActivityWatch#682 / ActivityWatch#685). It makes the miss diagnosable: - 3-level peers come from list_remote_dbs + select_remote_dbs_by_device_id (same pair pull_all uses), so duplicate device_id reporting matches pull - status-only overlay on top: 2-level leftovers, unrecognised entries, own-staging vs peer, SyncLayout - warn! on pull when zero remotes are found, with skip reasons - `aw-sync status` inspects peer dbs read-only (no WAL sidecars) Stacked on ActivityWatch#686. ActivityWatch#684 Git-Session-Id: 1673d3c9-c19e-5588-bfa5-96436fd680e8
|
Targeting correction to my note on #685: the dead
Also worth dropping the Separately, the #687 rework looks right: |
`aw-sync status` was a third directory walk beside list_remote_dbs (ActivityWatch#686) and collect_db_files (ActivityWatch#685). 3-level peers now come from the same list_remote_dbs + select_remote_dbs_by_device_id pair pull_all uses, so duplicate-device_id "not pulled" matches the pull path. Leftover 2-level / unrecognised entries sit on top of that list. ActivityWatch#687 Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
pull_all switched to list_remote_dbs, so get_remotes and its two helpers (contains_db_file, contains_subdir_with_db_file) had no callers. Clippy would not flag them because get_remotes was pub. Also drop the stale TODO that item 5 of ActivityWatch#684 already resolved. Two walkers remain, on purpose: - list_remote_dbs + select_remote_dbs_by_device_id — pull_all, 3-level - find_remotes + find_remotes_nonlocal — advanced sync_run, relative ActivityWatch#686 Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
The default `aw-sync`/`aw-sync daemon` path drove `sync_run` against the
sync root, writing `{device_id}/test.db` and scanning two levels — so it
never saw `{hostname}/{device_id}/*.db` remotes from Android or
`aw-sync sync`. Route the default daemon through `sync_wrapper` like
the simple `sync` command, and only stage a local db on push.
Walker rewrite dropped: `pull_all` already uses `list_remote_dbs`
(3-level-only) after ActivityWatch#686. Broadening
`find_remotes` at the sync root would import the ActivityWatch#682 orphan.
Fixes ActivityWatch#682
Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
`aw-sync status` was a third directory walk beside list_remote_dbs (ActivityWatch#686) and collect_db_files (ActivityWatch#685). 3-level peers now come from the same list_remote_dbs + select_remote_dbs_by_device_id pair pull_all uses, so duplicate-device_id "not pulled" matches the pull path. Leftover 2-level / unrecognised entries sit on top of that list. ActivityWatch#687 Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
|
@ErikBjare Dropped the trio here: Two walkers left, with a comment saying why they differ:
|
The default `aw-sync`/`aw-sync daemon` path drove `sync_run` against the
sync root, writing `{device_id}/test.db` and scanning two levels — so it
never saw `{hostname}/{device_id}/*.db` remotes from Android or
`aw-sync sync`. Route the default daemon through `sync_wrapper` like
the simple `sync` command, and only stage a local db on push.
Walker rewrite dropped: `pull_all` already uses `list_remote_dbs`
(3-level-only) after ActivityWatch#686. Broadening
`find_remotes` at the sync root would import the ActivityWatch#682 orphan.
Fixes ActivityWatch#682
Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
`aw-sync status` was a third directory walk beside list_remote_dbs (ActivityWatch#686) and collect_db_files (ActivityWatch#685). 3-level peers now come from the same list_remote_dbs + select_remote_dbs_by_device_id pair pull_all uses, so duplicate-device_id "not pulled" matches the pull path. Leftover 2-level / unrecognised entries sit on top of that list. ActivityWatch#687 Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
…arnings (#687) * feat(aw-sync): add `status` doctor command and fail-loud empty-pull warnings `aw-sync daemon` can walk a configured sync dir and stay silent when it finds nothing to pull, which is indistinguishable from a working setup. This does not change which remotes are pulled (#682 / #685). It makes the miss diagnosable: - classify both 2-level (`{device_id}/*.db`) and 3-level (`{hostname}/{device_id}/*.db`) layouts without opening sqlite - warn! on pull when zero remotes are found, with skip reasons - `aw-sync status` prints every entry, inspects peer dbs read-only (no WAL sidecars), and flags duplicate device_id, hostname mismatch, unpublished staging, and unimported peers #684 Git-Session-Id: 375e1ec2-04d0-5884-9beb-cc5cd9c704c6 * refactor(aw-sync): build status scan on RemoteDb walker `aw-sync status` was a third directory walk beside list_remote_dbs (#686) and collect_db_files (#685). 3-level peers now come from the same list_remote_dbs + select_remote_dbs_by_device_id pair pull_all uses, so duplicate-device_id "not pulled" matches the pull path. Leftover 2-level / unrecognised entries sit on top of that list. #687 Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
The default `aw-sync`/`aw-sync daemon` path drove `sync_run` against the
sync root, writing `{device_id}/test.db` and scanning two levels — so it
never saw `{hostname}/{device_id}/*.db` remotes from Android or
`aw-sync sync`. Route the default daemon through `sync_wrapper` like
the simple `sync` command, and only stage a local db on push.
Walker rewrite dropped: `pull_all` already uses `list_remote_dbs`
(3-level-only) after ActivityWatch#686. Broadening
`find_remotes` at the sync root would import the ActivityWatch#682 orphan.
Fixes ActivityWatch#682
Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
The default `aw-sync`/`aw-sync daemon` path drove `sync_run` against the
sync root, writing `{device_id}/test.db` and scanning two levels — so it
never saw `{hostname}/{device_id}/*.db` remotes from Android or
`aw-sync sync`. Route the default daemon through `sync_wrapper` like
the simple `sync` command, and only stage a local db on push.
Walker rewrite dropped: `pull_all` already uses `list_remote_dbs`
(3-level-only) after ActivityWatch#686. Broadening
`find_remotes` at the sync root would import the ActivityWatch#682 orphan.
Fixes ActivityWatch#682
Git-Session-Id: eca21d8a-f6da-5096-b28f-783778f4c09f
Problem
When one physical device has written under two hostname folders in the sync directory,
pull_allimported both. Provenance is the buckethostname(or$aw.sync.origin), so both folders land in the same destination bucket. Resume-from-newest then silently drops everything older than the first folder's newest event.Real-world case from #683:
POCO F8 Ultra/(9.4 MB, Jul 2026) vspoco_f8_ultra/(273 MB, 2021–2026). If the small folder is walked first, ~1M events never import and the log says✓ Already up to date!.The duplicate folders come from ActivityWatch/aw-android#272 (hostname sanitization without migration). Any hostname change or folder rename reproduces it.
What changed
pull_alldeduplicates bydevice_id. Discover{hostname}/{device_id}/*.db, group bydevice_id, keep the largest file,warn!the skipped folders. This is the hazard removal from the issue.find_remotes_nonlocalapplies the same collapse. So a later 3-level walker (aw-sync:daemon(the default subcommand) never pulls — two incompatible sync-folder layouts #682) does not reintroduce the bug on the daemon path.sync_one. If a resume-from-newest pull reports up-to-date but the source has more events than the destination, log that older history was not imported and that recovering it requires deleting the destination bucket and re-pulling.Not in this PR
device_idthe provenance key instead of hostname (suggested fix 2) — that's the aw-sync: move sync provenance out of the bucket ID into trustworthy bucket metadata #649 provenance work, breaking for existing synced-from bucket IDs.Already-truncated destination buckets are not repaired by this PR. Delete the dest bucket and re-pull after this lands.
Testing
cargo test -p aw-sync --offline: 17 lib tests + 10tests/sync.rs+ 3tests/sync_roundtrip.rs