You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reproduced, and it interacts badly with #685 — worth deciding before v0.14.0 ships.
#658 made bucket_new reject hostnames containing whitespace, with a carve-out for buckets that already exist. On the pull path, get_or_create_sync_bucket creates the destination bucket with the source bucket's hostname, unchanged. For any peer whose bucket hostname column contains a space, that first create is a 400, and the error aborts the entire sync pass.
Reproduction
Against the shipped v0.14.0-beta.5 (rust) binary from the bundle, on a throwaway db:
$ curl -X POST "localhost:5699/api/0/buckets/aw-watcher-android-synced-from-POCO%20F8%20Ultra" \ -H "Content-Type: application/json" \ -d '{"id":"aw-watcher-android-synced-from-POCO F8 Ultra","type":"currentwindow", "client":"aw-sync","hostname":"POCO F8 Ultra","data":{}}'{"message":"Invalid hostname \"POCO F8 Ultra\": hostname may not contain whitespace"}HTTP 400
$ curl -X POST ".../aw-watcher-android-synced-from-poco_f8_ultra" \ -d '{... "hostname":"poco_f8_ultra" ...}'HTTP 200
The first request is exactly what a pull of my POCO F8 Ultra constructs.
Why the peer hostname has a space
ActivityWatch/aw-android#272: aw-android added sanitizeDeviceHostname and renamed the sync folder to poco_f8_ultra, but never migrated the hostname column on the buckets. Read from the live staging db in my sync folder:
aw-watcher-android | POCO F8 Ultra | aw-android | currentwindow
aw-watcher-android-unlock | POCO F8 Ultra | aw-android | os.lockscreen.unlocks
aw-stopwatch | POCO F8 Ultra | aw-webui | general.stopwatch
And get_or_create_sync_bucket derives the destination from the bucket's hostname, not the folder name. So the sanitized name never reaches the create.
This is not an exotic device name. Any Android phone whose name had a space before the sanitization shipped is affected — "Pixel 8 Pro", "Galaxy S24 Ultra", and so on.
Blast radius
The failure chain: create_bucket → 400 → map_err(…)? in get_or_create_sync_bucket → aborts sync_datastores → aborts sync_run → ? in pull_all → whole pass aborts, every peer after it skipped → daemon returns Err → exits → supervisor restarts → identical failure → budget exhausted → dialog, sync stopped (see #688).
Scope, stated precisely:
Only when the destination server is aw-server-rust. aw-server-python has no whitespace check — I grepped aw_server/api.py / rest.py and there is none.
The desktop bundle's default server is aw-server-python, so a default desktop install is unaffected.
Android is affected — it embeds aw-server-rust. An Android device pulling a whitespace-hostname peer fails.
Desktop users who opt into aw-server-rust are affected.
Why it is latent today and why that changes
Nothing on desktop currently pulls — that is #682. #685 turns pulling on. So merging #685 activates this for every aw-server-rust destination that has such a peer in its sync folder. The fix for #682 is what makes this reachable.
Options
Naive sanitize-on-import is wrong — see the correction note below before implementing.
Sanitize on import, reusing the legacy ID.get_or_create_sync_bucket must first look up the existing unsanitized destination ID (…-synced-from-POCO F8 Ultra) and keep using it if present; only new buckets get a sanitized ID. The raw hostname is preserved in bucket metadata either way. Without the lookup this forks (below).
Suggest 1 (with the legacy-ID lookup) + 3 for v0.14.0, with 4 following on Android's own schedule.
Correction: why plain sanitizing forks
Anyone who pulled this device before #658 landed (Aug 26) already has a local bucket named …-synced-from-POCO F8 Ultra. If import starts sanitizing, it constructs …-synced-from-POCO_F8_Ultra, get_or_create_sync_bucket misses the existing bucket, and resume-from-newest starts from nothing — a full re-import, so every event appears twice in /timeline. That is the exact symptom of discussions#1373 that #648 was filed to fix.
The same fork hits aw-server-python destinations, which never needed sanitizing in the first place and would be broken by a fix aimed at a rust-only problem.
So the destination ID must be resolved as: existing legacy ID if one exists → otherwise sanitized ID. Sanitize the hostname field and new IDs, never an established one.
Credit: derived from a design review of aw-sync; I reproduced it against the shipped binary before filing.
Reproduced, and it interacts badly with #685 — worth deciding before v0.14.0 ships.
#658 made
bucket_newreject hostnames containing whitespace, with a carve-out for buckets that already exist. On the pull path,get_or_create_sync_bucketcreates the destination bucket with the source bucket's hostname, unchanged. For any peer whose buckethostnamecolumn contains a space, that first create is a 400, and the error aborts the entire sync pass.Reproduction
Against the shipped
v0.14.0-beta.5 (rust)binary from the bundle, on a throwaway db:The first request is exactly what a pull of my POCO F8 Ultra constructs.
Why the peer hostname has a space
ActivityWatch/aw-android#272: aw-android added
sanitizeDeviceHostnameand renamed the sync folder topoco_f8_ultra, but never migrated thehostnamecolumn on the buckets. Read from the live staging db in my sync folder:And
get_or_create_sync_bucketderives the destination from the bucket's hostname, not the folder name. So the sanitized name never reaches the create.This is not an exotic device name. Any Android phone whose name had a space before the sanitization shipped is affected — "Pixel 8 Pro", "Galaxy S24 Ultra", and so on.
Blast radius
The failure chain:
create_bucket→ 400 →map_err(…)?inget_or_create_sync_bucket→ abortssync_datastores→ abortssync_run→?inpull_all→ whole pass aborts, every peer after it skipped → daemon returnsErr→ exits → supervisor restarts → identical failure → budget exhausted → dialog, sync stopped (see #688).Scope, stated precisely:
aw_server/api.py/rest.pyand there is none.Why it is latent today and why that changes
Nothing on desktop currently pulls — that is #682. #685 turns pulling on. So merging #685 activates this for every aw-server-rust destination that has such a peer in its sync folder. The fix for #682 is what makes this reachable.
Options
Naive sanitize-on-import is wrong — see the correction note below before implementing.
get_or_create_sync_bucketmust first look up the existing unsanitized destination ID (…-synced-from-POCO F8 Ultra) and keep using it if present; only new buckets get a sanitized ID. The raw hostname is preserved in bucket metadata either way. Without the lookup this forks (below).Suggest 1 (with the legacy-ID lookup) + 3 for v0.14.0, with 4 following on Android's own schedule.
Correction: why plain sanitizing forks
Anyone who pulled this device before #658 landed (Aug 26) already has a local bucket named
…-synced-from-POCO F8 Ultra. If import starts sanitizing, it constructs…-synced-from-POCO_F8_Ultra,get_or_create_sync_bucketmisses the existing bucket, and resume-from-newest starts from nothing — a full re-import, so every event appears twice in/timeline. That is the exact symptom of discussions#1373 that #648 was filed to fix.The same fork hits aw-server-python destinations, which never needed sanitizing in the first place and would be broken by a fix aimed at a rust-only problem.
So the destination ID must be resolved as: existing legacy ID if one exists → otherwise sanitized ID. Sanitize the hostname field and new IDs, never an established one.
Credit: derived from a design review of aw-sync; I reproduced it against the shipped binary before filing.
Related: #685, #688, #682, ActivityWatch/aw-android#272, #658, ActivityWatch/activitywatch#1445.
cc @TimeToBuildBob