Skip to content

feat(aw-sync): add status doctor command and fail-loud empty-pull warnings - #687

Merged
ErikBjare merged 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/aw-sync-status-loud-diagnostics
Sep 16, 2026
Merged

ErikBjare merged 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:feat/aw-sync-status-loud-diagnostics

Conversation

@TimeToBuildBob

@TimeToBuildBob TimeToBuildBob commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #684 items 1–2 (fail loud + aw-sync status). Does not change which remotes are pulled — that is #685.

Stacked on #686 (RemoteDb walker). Suggested merge order: #686 → this → #685 rebased. #685 conflicts with this in aw-sync/src/util.rs; #686 and this compose clean.

The problem

Setting up sync and having it silently do nothing is currently indistinguishable from a working setup. The report that prompted this: 102 daemon passes, Pulling... 102 times, zero remotes found, zero warnings.

sync_run() only logs remotes when the 2-level walk finds some. Finding zero peers in a directory the user configured for sync is the interesting case.

What this PR does

  1. Fail loud. On pull, if find_remotes_nonlocal returns nothing (or skips classified peers), warn! with every sync-dir entry and why it was not pulled. Opening a remote db that fails also warn!s before returning the error.
  2. aw-sync status. Read-only doctor command. Does not create staging files. Prints:
    • sync dir, profile, hostname, device_id, server reachability
    • every entry classified as peer / own-staging / unrecognised, 2-level vs 3-level
    • per-db inspect (hostname, bucket/event counts, newest event) via SQLITE_OPEN_READ_ONLY so it does not write -wal/-shm into a Syncthing folder
    • warnings: duplicate device_id across folders (aw-sync: duplicate folders for one device_id silently truncate history on pull #683), folder name ≠ bucket hostname, unpublished own staging, unimported peers, leftover …-synced-from-… buckets, test.db name
  3. Consumes fix(aw-sync): skip duplicate device_id folders that truncate history on pull #686's RemoteDb walker. 3-level peers come from list_remote_dbs + select_remote_dbs_by_device_id (the same pair pull_all uses), so duplicate-device_id "not pulled" is what pull would actually skip. Status-only overlay on top: 2-level leftovers, unrecognised entries, own-staging vs peer, SyncLayout.

Not in this PR (called out in #684)

  • Per-device manifest.json (item 3) — status currently opens sqlite read-only; a manifest is the cheaper follow-up.
  • Raw Data / aw-webui peer surfacing (item 4) — different repo.
  • First-run / autostart_modules (item 6).
  • Walker unification for the daemon path (item 5) — fix(aw-sync): make default daemon use host-layout pull/push #685, rebase after this.

Tests

cargo test -p aw-sync --lib:

  • both layouts classified; empty-pull warning names skipped entries and duplicate device_id
  • a 2-level peer that pull already found does not trigger the empty-pull warning
  • read-only inspect returns hostname / counts / newest event
  • status keeps the same duplicate winner as select_remote_dbs_by_device_id

@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.61111% with 185 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.63%. Comparing base (656f3c9) to head (42d6128).
⚠️ Report is 113 commits behind head on master.

Files with missing lines Patch % Lines
aw-sync/src/status.rs 0.00% 123 Missing ⚠️
aw-sync/src/util.rs 78.12% 49 Missing ⚠️
aw-sync/src/sync.rs 0.00% 12 Missing ⚠️
aw-sync/src/main.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #687      +/-   ##
==========================================
+ Coverage   70.81%   79.63%   +8.82%     
==========================================
  Files          51       72      +21     
  Lines        2916     6817    +3901     
==========================================
+ Hits         2065     5429    +3364     
- Misses        851     1388     +537     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

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.

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob The doctor command is the right shape, and the read-only handling is the detail I would have asked for: opening peer dbs READ_ONLY so status cannot drop -wal/-shm files into a Syncthing folder matters — a diagnostic that perturbs the thing it diagnoses would be worse than no diagnostic, especially in a directory where every write gets replicated to every device.

Two things.

Merge order

Your note on #684 says this and #685 "touch the same files in different hunks and should merge either order". They do not:

#685 + #687 => CONFLICT  aw-sync/src/util.rs  (2 hunks, ~430 and ~180 lines)
#686 + #687 => clean

Reproduce:

git fetch origin pull/685/head:pr-685 pull/687/head:pr-687
git worktree add --detach /tmp/mt pr-685 && cd /tmp/mt
git merge --no-commit --no-ff pr-687

MERGEABLE on each PR is only against master. This one composes cleanly with #686; the collision is specifically with #685.

Build the scan on #686's RemoteDb

scan_sync_dir / classify_top_dir / db_entry / SyncDirEntry / SyncLayout is a third independent walk of the sync directory, alongside collect_db_files (#685) and list_remote_dbs (#686), on top of the existing find_remotes / find_remotes_nonlocal / get_remotes. #684 item 5 asked to collapse those three into one, and the stack currently takes it to five or six.

Most of what status needs to report is already in RemoteDb { hostname, device_id, path, size }. The parts that are genuinely status-only — classifying unrecognised entries, SyncLayout, the "own staging, unpublished" and "peer not imported" determinations — sit naturally on top of that list rather than beside it. pull_discovery_warnings and select_remote_dbs_by_device_id are also solving the same duplicate-device_id question twice; status should report what the pull path would actually do, which is only guaranteed if it calls the same function.

Suggested order for the stack: #686 → this → #685 rebased. #686 and this already merge clean, and both get simpler if status consumes the RemoteDb walker instead of adding its own.

Not blocking, and I would rather have this merged than perfect — of the three PRs this is the one that turns "my phone isn't syncing and there is nothing in the log" into a single command, which was the actual complaint behind #684.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@ErikBjare You're right — MERGEABLE was only vs master, and the "either order" note on #684 was wrong. Reproduced the conflict: #685 + this collide in aw-sync/src/util.rs; #686 + this compose clean.

Pushed a restack onto #686 (7a2cf22):

  • scan_sync_dir now starts from list_remote_dbs + select_remote_dbs_by_device_id (the same pair pull_all uses), so duplicate-device_id "not pulled" is what pull would actually skip
  • Status-only overlay on top of that list: 2-level leftovers, unrecognised entries, own-staging vs peer, SyncLayout
  • Test status_keeps_the_same_duplicate_winner_as_pull pins the doctor to the pull selector
  • READ_ONLY inspect unchanged

#685 still needs a rebase after these two land. This PR currently includes #686's commit until that one merges — then GitHub drops it from the diff.

Still pull-only on this repo, so not self-mergeable. Ready for a maintainer click; this can land first (carries #686) or after #686.

TimeToBuildBob added a commit to TimeToBuildBob/aw-server-rust that referenced this pull request Sep 16, 2026
`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
@TimeToBuildBob
TimeToBuildBob force-pushed the feat/aw-sync-status-loud-diagnostics branch from 7a2cf22 to b26b635 Compare September 16, 2026 07:21
TimeToBuildBob added a commit to TimeToBuildBob/aw-server-rust that referenced this pull request Sep 16, 2026
`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
@TimeToBuildBob
TimeToBuildBob force-pushed the feat/aw-sync-status-loud-diagnostics branch from b26b635 to 1ad9f8a Compare September 16, 2026 07:24
@ErikBjare

Copy link
Copy Markdown
Member

Heads-up: this went CONFLICTING when #686 merged (master is now 876709e). #685 is still green and mergeable. Needs a rebase onto master — the RemoteDb code it builds on is now upstream, so the rebase should mostly be dropping the 4c9fac6 base commit.

Also relevant to the status output: #695 proposes sync_run return a SyncReport instead of (). Right now status can describe the folder but not the last pass, because a pass keeps nothing. If that lands, status gains "what the last run actually did" for free — worth leaving room for it in the output shape rather than designing around its absence.

…arnings

`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 (ActivityWatch#682
/ ActivityWatch#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

ActivityWatch#684

Git-Session-Id: 375e1ec2-04d0-5884-9beb-cc5cd9c704c6
`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
@TimeToBuildBob
TimeToBuildBob force-pushed the feat/aw-sync-status-loud-diagnostics branch from 1ad9f8a to 42d6128 Compare September 16, 2026 07:56
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Rebased onto current master (876709e, the #686 squash).

The branch was 4c9fac6 + 00719dd + f191c5f + two status commits. After the squash those three are already upstream, so this is --onto origin/master f191c5f: the two status commits only.

New head: 42d6128. git log origin/master..HEAD:

  • dd68e0e feat(aw-sync): add status doctor command and fail-loud empty-pull warnings
  • 42d6128 refactor(aw-sync): build status scan on RemoteDb walker

Local check: cargo test -p aw-sync --offline (22+10+3), cargo fmt --check, cargo clippy -p aw-sync -- -D warnings all green. CI will re-run on the new head.

This is step 1 of the v0.14.0 sequence on ActivityWatch/activitywatch#1445. After this merges, next is the two in-PR additions on #685.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@ErikBjare
ErikBjare merged commit 988e606 into ActivityWatch:master Sep 16, 2026
7 checks passed
@ErikBjare

Copy link
Copy Markdown
Member

@greptileai review

@ErikBjare

Copy link
Copy Markdown
Member

@TimeToBuildBob review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@ErikBjare Post-merge review of 988e606. Greptile has not posted; this is from the landed tree.

What is right

Finding: not_visible_to_daemon does not describe the daemon

Two walkers, still:

Path Walker Sees
aw-sync / daemonsync_run find_remotes (2-level) {device_id}/*.db
aw-sync sync (simple) → pull_all list_remote_dbs (3-level) {hostname}/{device_id}/*.db

status classifies from the pull_all pair. remote_db_to_entry sets not_visible_to_daemon = None for a 3-level winner. db_entry does the same for a 2-level peer.

So:

  • A 3-level Android peer prints as [peer 3-level] with no "not pulled" reason.
  • The daemon still does not pull it (find_remotes never looks at depth 3).
  • pull_discovery_warnings is called from sync_run with that scan. The original failure (102 daemon passes, 3-level peers, zero remotes) now dumps those peers under "Found 0 remote db files" without saying why the daemon skipped them.

The field name promises daemon visibility. The implementation reports pull_all visibility, and only own/duplicate. Neither path's layout miss is named.

pull_all itself still info!("No remote databases found") and returns Ok(()) — fail-loud did not land on the simple sync path.

Not a revert. It is the sentence the doctor exists to print. Suggested follow-up, no pull-behaviour change:

  • 3-level peer: not pulled by daemon: 2-level walker (find_remotes); visible to aw-sync sync / pull_all
  • 2-level peer: not pulled by pull_all: 3-level-only walker; visible to daemon
  • Rename the field, or stop calling it not_visible_to_daemon until that is true.

Tests today encode the weak behaviour (classifies_both_layouts_and_flags_duplicate_device_id asserts the dump contains "3-level", not a walker reason).

Smaller notes

No code change from this review. The walker labels are the one follow-up I would take next, unless you want them left until the v2 devices/{device_id}/ switch makes both walkers obsolete.

ErikBjare pushed a commit that referenced this pull request Sep 18, 2026
* feat(aw-sync): daemon pull is opt-in via aw-sync config.toml (#714)

Erik's b8 decision: 0.14 is push-only everywhere unless the user opts
in. Add the first proper config for aw-sync's config dir
(dirs::get_config_dir(), previously a TODO stub):

  # aw-sync config
  pull = false   # default; set true to import peers every pass

Daemon subcommand: --mode changes from a defaulted SyncMode to
Option<SyncMode> so "not given" is distinguishable from an explicit
choice. Effective mode is push-only when the config's pull=false,
both when pull=true, and an explicit --mode always overrides the
config (dirs::effective_daemon_mode). The one-shot `aw-sync sync`
command is untouched -- it keeps its own pull+push default.

The file is created with the commented default on first daemon start
(or `status`), so users find the switch. `aw-sync status` now prints
the effective mode and where the config was read from.

No separate suppression was needed for the #687 "zero peers" warning:
sync_run() already only captures pull-discovery warnings when mode is
Pull/Both, so a push-only daemon pass naturally produces none.

Git-Session-Id: 2f40c8f4-6fa5-5c1d-9313-e9e188acb41c

* fix(aw-sync): status reads config without creating it; suppress per-peer pull warnings

Two fixes from #716 review (Erik, 2026-09-18):

1. `status` is a read-only doctor. Swap `load_or_create_sync_config` for the
   new `read_sync_config` helper which returns `None` when the file is absent
   instead of writing the default. When absent, status now prints:
     daemon mode: push (pull=false, config: <path> — not present, default)

2. When `pull = false`, listing every peer with "! has not been imported
   locally" is misleading — the daemon is doing the right thing. Replace the
   per-peer warnings with one top-level note:
     pull is off in <config>; peers below are visible but not imported by
     the daemon (set pull = true, or run `aw-sync sync`)

Also adds two tests for `read_sync_config` (absent → None, no file created;
present + pull=true → Some(config)).

Git-Session-Id: d398

* fix(aw-sync): explicit --mode daemon startup no longer depends on config.toml

An explicit --mode is documented to always win over config.toml, but the
daemon still unconditionally loaded/created the config file first. A
malformed or unwritable config.toml would then abort startup before
daemon() ran, even with an explicit CLI override.

Skip config loading entirely when --mode is given explicitly.

Git-Session-Id: 212461ef-20e7-561e-b99b-a8f641b73b9a

* fix(aw-sync): status never creates config dir; clarify config-derived mode is a guess

- config_dir_path() constructs the aw-sync config path without creating it;
  get_config_dir() (used by the daemon, which is about to write config.toml
  there) now delegates to it. status previously called get_config_dir()
  before read_sync_config(), so the read-only doctor command silently
  created the config directory as a side effect.
- status's 'daemon mode:' line is derived from config.toml alone and cannot
  see an explicit --mode passed to a running daemon; label it as such and
  point at the 'Last pass' section, which reports the mode actually used.

Git-Session-Id: a7e90efc-d5b6-5d9b-a826-55b00e53d16c

* fix(aw-sync): namespace daemon config under [daemon] table

Erik: 'Properties should be in some default namespace, not in
top-level imo.' Follows aw-server's own convention of nesting a
distinguishable sub-concern in its own [section] (cf. [auth] in
aw-server/src/config.rs) rather than dumping it at the config's
top level. pull now lives at [daemon].pull; SyncConfig wraps a new
DaemonConfig. Updated the commented default, all call sites, and
the dirs.rs unit tests.

Git-Session-Id: bcd5de1a-146f-5d49-a776-ae61c28c2602
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

aw-sync: sync failures are silent — add aw-sync status, loud diagnostics, per-device manifest, and surface peers in Raw Data

2 participants