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
With sync enabled, the app is killed by SIGABRT roughly half a second into every sync cycle. The abort happens inside libaw_sync.so, so performSyncAsync never returns from syncFn() and the SAF mirror added in #209 is never reached — the sync directory is populated internally but nothing is ever copied to the user-chosen SAF directory.
This affects both JNI entry points: syncBoth and syncPush abort identically, so it is not specific to the pull path.
Environment
Device: OnePlus Nord CE 3 5G (CPH2493 / OP556FL1), arm64
Open Sync Settings, enable the toggle, choose an SAF directory in shared storage (e.g. /sdcard/ActivityWatchSync).
Wait ~1 minute for SyncScheduler's first sync.
Observed
08:04:29.565 I SyncInterface: Starting sync operation: Full Sync
08:04:30.277 F DEBUG : signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
08:04:30.277 F DEBUG : pid: 25108, tid: 25696, name: pool-6-thread-1
08:04:30.277 F DEBUG : #00 pc 000000000008b70c libc.so (abort+156)
08:04:30.277 F DEBUG : #01 pc 00000000012eb124 base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG : #02 pc 00000000012e7d5c base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG : #03 pc 00000000012d8c80 base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG : #04 pc 00000000012e81f8 base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG : #05 pc 00000000012d7fa4 base.apk (offset 0x2f30000)
08:04:30.277 F DEBUG : #15 pc 00000000006d4c28 base.apk (Java_net_activitywatch_android_SyncInterface_syncBoth+260)
08:04:30.277 F DEBUG : #25 ... SyncInterface$syncBothAsync$2.invoke
08:04:30.277 F DEBUG : #33 ... SyncInterface.performSyncAsync$lambda$4
Patching SyncScheduler.performSync() to call syncPushAsync instead produces the same abort, twice in one capture:
08:14:10.059 I SyncInterface: Starting sync operation: Push
08:14:10.579 F libc : Fatal signal 6 (SIGABRT) in tid 8024 (pool-6-thread-1)
#15 ... (Java_net_activitywatch_android_SyncInterface_syncPush+260)
08:15:23.946 I SyncInterface: Starting sync operation: Push
08:15:24.024 F libc : Fatal signal 6 (SIGABRT) in tid 9839 (pool-5-thread-1)
#15 ... (Java_net_activitywatch_android_SyncInterface_syncPush+260)
08:15:24.528 I Zygote : Process 8448 exited due to signal 6 (Aborted)
Note both entry points abort at the same +260 offset.
In aw-sync/src/sync.rs, sync_run fetches info from the local server, takes info.device_id, and only then calls setup_local_remote(path, device_id). Since the directory is named with the correct device id, both the get_info() call against the in-app aw-server-rust and the datastore creation must have succeeded. That places the abort downstream of both, in the bucket-sync step itself rather than in startup, the HTTP client, or directory setup.
SIGABRT with no fault address is consistent with a Rust panic, but the panic message never reaches logcat (Rust writes panics to stderr, which Android discards), and the shipped .so has no .debug_info, so addr2line only resolves to the nearest exported symbol. A debug build, or routing the Rust panic hook to android_log, would identify the frame immediately.
Suggested follow-ups
Install a panic hook that logs via android_log so panics are diagnosable from logcat at all — useful well beyond this bug.
Publish a symbol-bearing build (or upload symbols) so tombstones can be symbolicated.
Secondary observations
Two smaller things found while investigating, happy to open separate issues or PRs if useful:
SyncSettingsActivity is unreachable from the UI. It is registered only in res/menu/main.xml (the options menu), but MainActivity never calls setSupportActionBar(), the theme is AppTheme.NoActionBar, and the Toolbar in res/layout/app_bar_main.xml has been commented out since 4a02a0b6 (2018). So onCreateOptionsMenu inflates a menu with no host and no overflow button is rendered, while activity_main_drawer.xml has no sync entry. Adding a nav_sync_settings item to the drawer (mirroring nav_auth_settings) makes it reachable.
The SAF mirror in #209 copies nothing even when sync succeeds.copySyncFilesToSafDir() does a flat File(syncDir).listFiles()?.filter { it.isFile }, but aw-sync never writes a regular file at the root of the sync directory — setup_local_remote writes <sync_dir>/<device_id>/test.db, and on Android the observed tree is <sync_dir>/<hostname>/<device_id>/test.db. So the filter matches zero entries. The receiving side also requires the nesting: find_remotes in aw-sync/src/util.rs keeps only directories and looks for *.db one level inside them, so a flattened copy would be ignored even if it were made. A recursive, structure-preserving mirror fixes both halves.
Summary
With sync enabled, the app is killed by
SIGABRTroughly half a second into every sync cycle. The abort happens insidelibaw_sync.so, soperformSyncAsyncnever returns fromsyncFn()and the SAF mirror added in #209 is never reached — the sync directory is populated internally but nothing is ever copied to the user-chosen SAF directory.This affects both JNI entry points:
syncBothandsyncPushabort identically, so it is not specific to the pull path.Environment
CPH2493/OP556FL1), arm64BP2A.250605.015)master@53a2fb6(includes feat(sync): add SyncSettingsActivity — enable/disable toggle + SAF directory picker #204 and feat(sync): bridge SAF-granted URI to aw-sync output (Phase 2) #209), debug variantlibaw_sync.so/libaw_server.sotaken unmodified from the CIBuildworkflow artifactsSteps to reproduce
/sdcard/ActivityWatchSync).SyncScheduler's first sync.Observed
Patching
SyncScheduler.performSync()to callsyncPushAsyncinstead produces the same abort, twice in one capture:Note both entry points abort at the same
+260offset.What this narrows it down to
The staging tree is created before the crash:
In
aw-sync/src/sync.rs,sync_runfetchesinfofrom the local server, takesinfo.device_id, and only then callssetup_local_remote(path, device_id). Since the directory is named with the correct device id, both theget_info()call against the in-appaw-server-rustand the datastore creation must have succeeded. That places the abort downstream of both, in the bucket-sync step itself rather than in startup, the HTTP client, or directory setup.SIGABRTwith no fault address is consistent with a Rust panic, but the panic message never reacheslogcat(Rust writes panics to stderr, which Android discards), and the shipped.sohas no.debug_info, soaddr2lineonly resolves to the nearest exported symbol. A debug build, or routing the Rust panic hook toandroid_log, would identify the frame immediately.Suggested follow-ups
android_logso panics are diagnosable fromlogcatat all — useful well beyond this bug.Secondary observations
Two smaller things found while investigating, happy to open separate issues or PRs if useful:
SyncSettingsActivityis unreachable from the UI. It is registered only inres/menu/main.xml(the options menu), butMainActivitynever callssetSupportActionBar(), the theme isAppTheme.NoActionBar, and theToolbarinres/layout/app_bar_main.xmlhas been commented out since4a02a0b6(2018). SoonCreateOptionsMenuinflates a menu with no host and no overflow button is rendered, whileactivity_main_drawer.xmlhas no sync entry. Adding anav_sync_settingsitem to the drawer (mirroringnav_auth_settings) makes it reachable.The SAF mirror in #209 copies nothing even when sync succeeds.
copySyncFilesToSafDir()does a flatFile(syncDir).listFiles()?.filter { it.isFile }, butaw-syncnever writes a regular file at the root of the sync directory —setup_local_remotewrites<sync_dir>/<device_id>/test.db, and on Android the observed tree is<sync_dir>/<hostname>/<device_id>/test.db. So the filter matches zero entries. The receiving side also requires the nesting:find_remotesinaw-sync/src/util.rskeeps only directories and looks for*.dbone level inside them, so a flattened copy would be ignored even if it were made. A recursive, structure-preserving mirror fixes both halves.