Repository navigation
Conversation
Build.MODEL returns the OEM marketing name (e.g. 'Poco F8 Ultra') which contains spaces that break hostname-based bucket matching in aw-webui and aw-server. Build.DEVICE returns the device codename (e.g. 'poco_f8_ultra') which is lower-case, underscore-delimited, and safe to use as a hostname. Fixes the class of bug reported in ActivityWatch#176 where Xiaomi/Poco devices produced space-containing hostnames.
5 tasks
Greptile SummaryThis PR makes Android hostname fallback and normalization safer. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (4): Last reviewed commit: "fix: filter blank DEVICE_NAME before fal..." | Re-trigger Greptile |
Settings.Global.DEVICE_NAME can itself contain spaces (e.g. 'Poco F8 Ultra') so the previous fix only addressed the fallback path. Sanitize the final hostname by trimming, lowercasing, and replacing spaces with underscores, ensuring bucket hostname matching works even when DEVICE_NAME is configured with a human-readable name.
Contributor
Author
|
@greptileai review |
Use strict regex [^a-z0-9_-]+ instead of only space replacement, so configured device names with quotes or other special chars don't break bucket JSON. Also guard whitespace-only DEVICE_NAME with takeIf so it falls through to Build.DEVICE rather than resolving to empty string.
Contributor
Author
|
@greptileai review |
Contributor
Author
|
@greptileai review |
Contributor
Author
|
CI-green and mergeable (Greptile 5/5) — 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
pushed a commit
that referenced
this pull request
Sep 17, 2026
* fix(android): migrate unsanitized hostname after sanitizer change Sanitizing Settings.Global.DEVICE_NAME (PR #183) forked already-syncing devices onto a new sync folder without moving the old one, and migrateHostname only rewrote unknown/Unknown so buckets kept names like "POCO F8 Ultra". Rewrite local bucket hostnames before the datastore opens, rename or drop the leftover folder for this device_id, and delete the matching stale SAF tree so Syncthing stops replicating the fork. Fixes #272 Git-Session-Id: 85705cb4-3c19-5fef-94f7-5b17e26c12ef * fix(android): harden sanitized-hostname migration edge cases Review follow-up on #273: - BackgroundService: queue the bucket-hostname rewrite behind serverStarted instead of skipping silently, so a failed first attempt still converges instead of skipping forever on every later start. - planFolderMigration: without a local device id, only fold a single unambiguous legacy candidate; never rename every legacy dir (could fold another device's data under the current hostname). - SyncInterface SAF cleanup: never delete a legacy hostname dir when the local device id is unavailable; deletion stays scoped to the local device's subdirectory otherwise. Git-Session-Id: 5bb5a763-f523-55ef-b462-cd8f5b49e049 * fix(android): run stale SAF hostname cleanup after a successful mirror Deleting the legacy hostname tree before mirroring let the mirror re-copy a leftover local legacy folder into the SAF tree, so the stale fork persisted; a cancelled/partial mirror could also leave SAF without data the local copy still holds. Cleanup now runs only after the mirror completes fully, and the local legacy folder is removed from SAF on each run after being mirrored. Git-Session-Id: f29f34fb-edc6-5e99-ad09-d77623ffdedd * fix(android): address Greptile P1/P2 on sanitized-hostname migration - Run migrateSanitizedHostnameIdentity and the sequenced server start on Dispatchers.IO (serialized across overlapping onStartCommand) so the sync-tree traversal and sqlite.db write transaction cannot ANR service startup. - Scope the bucket-hostname rewrite to locally-owned buckets with 'id NOT LIKE %-synced-from-%' (reserved remote-origin marker in aw-sync) so a synced peer's colliding hostname is never relabeled. - Guard the deferred rewrite so repeated starts while the server is running queue at most one polling thread per process. Git-Session-Id: cca8bd5d-395c-5568-905c-c44546218e4d * fix(android): serialize server start and deferred rewrite on the migration lock Server start now runs under sanitizedMigrationLock so overlapping onStartCommand invocations and the deferred rewrite thread are mutually exclusive with the server opening sqlite.db. The queued rewrite re-checks serverStarted under the lock before writing. Git-Session-Id: cca8bd5d-395c-5568-905c-c44546218e4d * fix(android): address AI-review P1s on hostname migration - SanitizedHostnameMigration: never wholesale-rename a no-device-id legacy dir that holds multiple device-id subdirs (could fold other devices' data under the current hostname) - SyncInterface: defer legacy-folder migration out of init to the first sync operation (init runs on the main thread; migration can ANR) - BackgroundService: drop the 60s bound on the deferred bucket rewrite — a long-running server would time out and the rewrite would never converge; poll until the server task exits instead Git-Session-Id: 50ca7f02-5783-539e-b180-594f5d69e18e * fix(android): retry folder migration on partial failure; stop rewrite poll at teardown - migrateSyncFolders returns MigrationResult(moved, failed); SyncInterface records migration complete only when failed == 0, so a failed rename or deletion is retried on the next sync instead of skipped. - Deferred bucket-hostname rewrite guard and thread reference moved to the companion object (recreated service instances shared one guard), and onDestroy interrupts the polling thread so teardown does not leak it. Git-Session-Id: 7a94d0aa-ed38-5149-9c62-bf4e3a65eb11 * fix(android): serialize folder migration process-wide BackgroundService (service start) and SyncInterface (first sync) both run SanitizedHostnameMigration.migrateSyncFolders; without a shared lock they could plan from and mutate the same directories concurrently. Git-Session-Id: 7a94d0aa-ed38-5149-9c62-bf4e3a65eb11 * fix(android): make sanitizedMigrationLock static The deferred rewrite thread outlives the service instance that queued it; an instance-level lock let a recreated instance's server start open the database while the old thread was still rewriting it. Git-Session-Id: 7a94d0aa-ed38-5149-9c62-bf4e3a65eb11 * fix(android): address Greptile P2s on hostname migration - BackgroundService: assign hostnameRewriteThread so cancelQueuedHostnameRewrite can actually interrupt the polling thread (was always a no-op: Thread{} created but reference never stored) - BackgroundService.onDestroy: call cancelQueuedHostnameRewrite() so the daemon thread stops polling when the service is torn down (comment said it did; code did not) - SanitizedHostnameMigration.planFolderMigration: when localDeviceId is null and the sanitized dir already exists, only plan DeleteHostnameDir for empty legacy dirs. Previously it always planned the delete; applyFolderMigration rejected it when the dir had device-id entries, making MigrationResult.failed > 0 and causing ensureLegacyFoldersMigrated to retry the same failing action on every sync. - Update test to assert correct (no-action) behavior for the non-empty-dir case and add a complementary test for the empty-dir (delete-eligible) case. Git-Session-Id: 1223 * fix(android): join cancelled rewrite thread on teardown to close recreation race Greptile P1: hostnameRewriteQueued lives in the companion object (shared across service instances), so a recreated service could observe the guard still set between cancelQueuedHostnameRewrite()'s interrupt() and the old thread's finally block, skip queueing, and miss the deferred rewrite until another full start (not guaranteed). cancelQueuedHostnameRewrite() now joins the interrupted thread (bounded, 3s > 1s poll interval) so the guard is settled before a recreated instance runs the migration; queueHostnameRewriteAfterServerExit() additionally re-queues when the guard is set but the owning thread is no longer alive. Git-Session-Id: 84cd2de6-e033-5129-98b8-2d5c7b78f268 * fix(android): cut hostname-rewrite cancel join to 500ms to bound main-thread teardown stall The interrupt() in cancelQueuedHostnameRewrite() wakes the poll sleep immediately, so the join only needs to cover poll-wake plus the worker's finally block (milliseconds). The previous 3s bound stalled the main thread in onDestroy() whenever the worker was mid-SQLite-rewrite, where interrupt cannot cancel the transaction; in that case the worker completes the rewrite and sets the preference itself, so a long join buys nothing. Git-Session-Id: 8d81f917-4443-5800-9969-89574183b0ba * ci: retrigger E2E after known NativeWindowInsetsTest.syncToggleReceivesRealTap flake The previous run failed only on syncToggleReceivesRealTap ("A screen tap must change the persisted setting expected:<true> but was:<false>") with no System UI ANR in the log. The failing head differs from its last green run on this branch only by an unrelated BackgroundService.kt change, and the job cannot be rerun (TimeToBuildBob is pull-only). No code change. Git-Session-Id: 293f1f27-ca8d-554e-b7ff-3be621a9cac7 * fix(android): stop retrying an undeletable legacy hostname dir, skip server start after teardown The planner counts subdirectories when deciding a legacy hostname dir is empty, but applyFolderMigration refuses to delete a dir with any entry. A legacy dir holding only regular files was therefore planned for deletion, rejected, counted as failed=1, and retried on every sync forever (ensureLegacyFoldersMigrated never marks itself done). migrateSyncFolders now passes the set of hostname dirs holding entries the planner cannot account for and does not plan a delete for those. The server start moved into an IO coroutine, which can run after onDestroy; it now re-checks a serviceDestroyed flag after acquiring sanitizedMigrationLock so a torn-down service does not start a server nobody owns. Service has no isDestroyed before API 35, hence the explicit flag. Git-Session-Id: 95fc8809-06f8-53bb-8aa0-0a522af14149
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.
Problem
getDeviceName()inRustInterface.ktandSyncInterface.ktfalls back toandroid.os.Build.MODELwhenSettings.Global.DEVICE_NAMEis null.Build.MODELreturns the OEM marketing name (e.g."Poco F8 Ultra") which contains spaces — that breaks hostname-based bucket matching in aw-webui and aw-server.Reported in #176: a Xiaomi Poco F8 Ultra produced
"Poco F8 Ultra"as its hostname. The webui then fails to associate it with the Android bucket view because the hostname contains spaces.Fix
Replace the
Build.MODELfallback withBuild.DEVICE, which returns the device codename (poco_f8_ultra— lowercase, underscore-delimited, no spaces). This is the correct format for a network hostname.Settings.Global.DEVICE_NAME"Poco F8 Ultra"Build.MODEL"Poco F8 Ultra"Build.DEVICE"poco_f8_ultra"Changes
RustInterface.kt:Build.MODEL→Build.DEVICESyncInterface.kt:Build.MODEL→Build.DEVICECloses #176 (contributes — the hostname fix is one part of the broader stability tracker).