Skip to content

fix(migration): handle plain-text success response from migrateHostname JNI#172

Open
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/hostname-migration-json-protocol
Open

fix(migration): handle plain-text success response from migrateHostname JNI#172
TimeToBuildBob wants to merge 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:fix/hostname-migration-json-protocol

Conversation

@TimeToBuildBob

Copy link
Copy Markdown
Contributor

Summary

Fixes a correctness bug where setHostnameMigrated() was never called, causing hostname migration to run on every app start indefinitely.

Root Cause

The native migrateHostname() JNI function returns:

  • Success: plain-text "Migrated hostname for N bucket(s)" (NOT JSON)
  • Error: JSON {"error": "message"}

BackgroundService was trying to parse the success response as JSON with a "success" boolean key that never exists. This always threw a JSONException, set migrationSucceeded = false, and skipped setHostnameMigrated() — so migration would re-run on every subsequent app start.

On fresh install this produced two spurious logcat warnings even though migration succeeded normally with 0 buckets:

I BackgroundService: Hostname migration result: Migrated hostname for 0 bucket(s)
W BackgroundService: Migration result was not valid JSON; will retry on next start
W BackgroundService: Hostname migration reported failure; will retry on next start

Fix

Check for the known plain-text success prefix first. Only attempt JSON parsing for the error case, extracting the "error" field for a single informative warning.

Test plan

  • Fresh install (no prior data): migration runs, setHostnameMigrated() is called, no spurious warnings, migration does not re-run on next start
  • Install with existing unknown/Unknown hostname buckets: migration renames them, setHostnameMigrated() called, migration does not re-run
  • Server not ready on first start (error path): single W Hostname migration failed (...) warning, migration retries on next start

Fixes #171

…me JNI

The native migrateHostname() returns a plain-text string on success
("Migrated hostname for N bucket(s)") but a JSON error object on failure
({"error": "..."}). The Kotlin code was incorrectly trying to parse the
success response as JSON, which always failed, so setHostnameMigrated()
was never called and migration would retry on every app start.

This also caused two spurious logcat warnings on fresh install:
  W Migration result was not valid JSON; will retry on next start
  W Hostname migration reported failure; will retry on next start

Fix: check for the known plain-text success prefix first. Only parse JSON
when the result doesn't match success, and extract the "error" field for
a single, informative warning instead of two misleading ones.

Fixes: ActivityWatch#171
@greptile-apps

greptile-apps Bot commented Jul 3, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a real correctness bug: migrateHostname() returns a plain-text success string, not JSON with a "success" key, so the old JSON-parse path always threw JSONException and setHostnameMigrated() was never called — causing migration to re-run on every app start.

  • Replaces the JSON-parse success check with a startsWith("Migrated hostname for") prefix test, reserving JSON parsing only for the error path to extract a meaningful error message.
  • Collapses two redundant log warnings into a single informative Log.w that includes the extracted error detail.

Confidence Score: 5/5

Safe to merge — the fix correctly addresses the root cause and the changed code path is small and well-scoped.

The old code always threw JSONException on a valid success response, so setHostnameMigrated() was never called. The new prefix check directly matches what the native lib actually returns, closing the loop correctly. The error path properly extracts the error field with a raw-string fallback, addressing the prior null-fallback concern. No new runtime failure modes are introduced.

No files require special attention; the single changed file has a focused, correct fix.

Important Files Changed

Filename Overview
mobile/src/main/java/net/activitywatch/android/BackgroundService.kt Migration success detection replaced with plain-text prefix check; the fix is correct for the described native protocol but the literal prefix string is unconstrained and brittle to future native lib changes.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant OS as Android OS
    participant BS as BackgroundService
    participant P as AWPreferences
    participant R as RustInterface (JNI)

    OS->>BS: onStartCommand()
    BS->>P: hasMigratedHostname()?
    P-->>BS: false
    BS->>BS: launch IO coroutine
    BS->>R: getDeviceName()
    R-->>BS: hostname
    BS->>R: migrateHostname(hostname)
    R-->>BS: ""Migrated hostname for N bucket(s)" OR {"error":"..."}"
    alt result.startsWith("Migrated hostname for")
        BS->>P: setHostnameMigrated()
        Note over BS,P: Migration marked done — won't re-run
    else JSON error or unknown response
        BS->>BS: extract errorMsg from JSON / raw string
        BS->>BS: Log.w("Hostname migration failed (errorMsg)")
        Note over BS: migrationSucceeded = false — retry next start
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant OS as Android OS
    participant BS as BackgroundService
    participant P as AWPreferences
    participant R as RustInterface (JNI)

    OS->>BS: onStartCommand()
    BS->>P: hasMigratedHostname()?
    P-->>BS: false
    BS->>BS: launch IO coroutine
    BS->>R: getDeviceName()
    R-->>BS: hostname
    BS->>R: migrateHostname(hostname)
    R-->>BS: ""Migrated hostname for N bucket(s)" OR {"error":"..."}"
    alt result.startsWith("Migrated hostname for")
        BS->>P: setHostnameMigrated()
        Note over BS,P: Migration marked done — won't re-run
    else JSON error or unknown response
        BS->>BS: extract errorMsg from JSON / raw string
        BS->>BS: Log.w("Hostname migration failed (errorMsg)")
        Note over BS: migrationSucceeded = false — retry next start
    end
Loading

Reviews (2): Last reviewed commit: "fix(migration): use raw result as fallba..." | Re-trigger Greptile

Comment thread mobile/src/main/java/net/activitywatch/android/BackgroundService.kt
When the JSON doesn't contain an 'error' key, optString('error', null)
logged '(null)'. Use result as fallback so the raw payload is always
visible for debugging.
@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

Addressed Greptile's P2 finding in ad07391: changed optString("error", null) to optString("error", result) so the raw payload is always visible in the warning log even when the JSON doesn't contain an "error" key.

Merge recommendation: Ready to ship. Summary:

  • Fixed: correctness bug where setHostnameMigrated() was never called (migration re-ran every start indefinitely)
  • Fixed: spurious JSON-parse warnings on fresh install (Spurious 'Migration result was not valid JSON' warning on fresh install #171)
  • Remaining (non-blocking): none — Greptile P2 diagnostic nitpick resolved in ad07391
  • CI: needs a green run post-push
  • Domain risk: none for the logic change; the plain-text prefix check is an exact match against the native lib's documented return format

Converged after 1 Greptile round.

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

@greptileai review

@TimeToBuildBob

Copy link
Copy Markdown
Contributor Author

All 5 checks now green including E2E tests. Ready for maintainer merge whenever you get a chance.

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.

Spurious 'Migration result was not valid JSON' warning on fresh install

1 participant