Skip to content

[Bug]: Pubky private-session recovery remains blocked after restart (shared_state_busy) #1453

Open

Description

@piotr-iohk

What happened?

After an ordinary app restart, the saved Pubky profile remains readable, but its private session does not become ready for editing. Edit/Add Tag are disabled and Try Again/Disconnect recovery controls are visible. @pubky_profile_2 fails its authenticated-readiness check while app logs repeatedly report shared_state_busy.

The October 9 scheduled staging run reproduced this on all three test retries after the public-profile display fix had merged. This is a remaining private-session availability problem, distinct from the earlier missing public profile/Copy symptom. The evidence does not identify which client holds the lock or when it releases.

Expected behavior

The same saved identity should regain an authenticated profile after transient startup contention clears, without requiring Disconnect or identity recreation. While recovery is pending, public profile/QR/Copy should remain readable and editing should remain disabled. The existing E2E gate waits 30 seconds for enabled Edit; that gate should not be removed merely because public display works. A broader product recovery-time requirement and the lock holder/release time still need investigation.

Steps to Reproduce

  1. Use a fresh regtest Bitkit wallet with staging Pubky services.
  2. Create a Bitkit Pubky profile, complete the Pay Contacts setup, and confirm the profile is editable.
  3. Change the name from Alice to Bob, add a note, website link and tag, and save.
  4. Add a valid staging contact and confirm the contact row is saved. Allow the existing five-second settling delay.
  5. Terminate and relaunch Bitkit without deleting the wallet or app data; allow the existing five-second post-launch delay.
  6. Open Profile, verify its public details and copied key, then check whether Edit becomes enabled. The test fails before its later recovery-phrase wallet-restoration steps.

Logs / Screenshots / Recordings

Scheduled staging run 37882043376, workflow attempt 1, failed Pubky/Paykit job. Full existing investigation: #1443 comment.

The readiness assertion failed at 04:20:02, 04:35:04 and 04:41:15 UTC:

Pubky restoration not ready after app restart: ProfileEdit did not become enabled within 30000ms

Continuous logcat from retry 3 records:

04:40:34.620 PubkyRepo.kt:527 Deferred session restoration, keeping saved session
  code=shared_state_busy, context=Pubky shared state remains locked; retry later
04:41:06.520 PubkyRepo.kt:527 Deferred session restoration, keeping saved session
  code=shared_state_busy, context=Pubky shared state remains locked; retry later

Wallet backups also reported the same code at 04:40:50 and 04:41:00. Initial creation/editing, contact save, readable restarted profile and copied key passed. The test stopped before contact verification after restart and before recovery-phrase restoration.

Retry 3: public profile visible while authenticated editing is unavailable

Complete failed-shard artifact, including retries and continuous logcat. The screenshot and recording are from test retry 3; the linked artifact contains all retries and continuous logcat.

Bitkit Version

2.5.0 (190), dev/debug E2E regtest build; Android master 50c9bc3478f9e5a37d1ea96880cce386fab8a6d3; Paykit 0.1.0-rc71. E2E 67fd0f355ed9673f1f0d81d9caf9c003786f638c (includes the settling/readiness assertions from E2E #270).

Device / OS

Pixel 6 Android emulator, Android 13 / API 33, x86_64 (GitHub Actions; pinned workflow configuration).

Reproducibility

Always in this recorded workflow attempt: 3/3 test retries failed at the same private-readiness gate. General frequency outside this run is TBD. The earlier reported local master pass is separate evidence, not a pass of this nightly.

Additional context

  • Public display was addressed by #1443; original report #1442 is closed.
  • Related shared-runtime latency tracking: Android #1419 and iOS #868.
  • Companion iOS report: #908. iOS reproduced private-readiness failure at chore: log cause of keychain load failures #898 head 0c8f56eb, with public display working.
  • No lost-contact, failed seed-restoration or confirmed SDK/server root-cause claim is made. Investigate the shared-state lock holder, pending writes/cleanup and session-recovery lifecycle across app, SDK and homeserver layers.
  • Keep waitForAuthenticatedProfileReady and its diagnostic. Other failures in the shard, including the separate profile-name assertion, are not attributed to this issue.

Activity

  1. added theissue type on Oct 9, 2026
  2. ben-kaufman commented on Oct 9, 2026

    @ben-kaufman
    Contributor

    Investigation update: I reproduced the blocked state in the real Android app and confirmed automatic recovery. I have not established a safe small code fix for the 30-second readiness requirement.

    Two recovery cases are verified:

    • A controlled SDK test against real Homeserver 0.15 aborted a read while holding the normal lock. Fresh initialization remained busy at 29s and recovered at 59.517s, with identical encrypted state before/after.
    • A disposable Android app using published rc71 native bytes was force-stopped during ongoing work, 7.755s after wallet backup started. Native logs explicitly recorded pending_writes=1 cooldown_seconds=300. The cooldown completed after 300.030s; the session then restored automatically, 373.588s after force-stop. Wallet backup succeeded and the real Edit Profile form opened with the same identity. No Retry/relaunch was needed. A read-only external check observed one pending marker during the wait and zero after recovery.

    The latter used an existing API36 ARM test build, not an exact replay of nightly master; APK SHA256 3f8fef597d80eb7d9c1ce521f9593b2c8118ab12153f14e66b2525b5ad3b7baa. Its native library exactly matches rc71; the relevant SDK storage/lock source is unchanged in rc72. No payments or profile edits were made.

    This does not identify the original nightly lock holder. Its three attempts stopped observation about 51-55s after restart, and backup timing alone does not prove which operation left a lock/marker. The new native warning does establish why this reproduction exceeded one minute.

    For now this is a confirmed limitation of the current recovery design, not evidence of a permanent app deadlock. Faster polling cannot free the lock, and bypassing initialization or clearing markers early would weaken safety. Backup deferral/snapshot reuse could reduce exposure but would not remove this interrupted-write wait; they are not validated fixes here. Keeping this issue open and the existing timeout assertion unchanged. The original holder and iOS incident still need attribution; no production patch or PR is claimed.

  3. piotr-iohk commented on Oct 12, 2026

    @piotr-iohk
    CollaboratorAuthor

    Follow-up from the October 12 scheduled staging run, workflow attempt 1: run 38202634070, app master 3cac93b090554526f0a5524c22e45bf978b6928a, published Paykit 0.1.0-rc72. E2E 492dbfcc890a0ab9d97bc801ffbe428f164dfc6e.

    The new artifact captures the longer recovery observation in all three test retries. Every retry eventually reached authenticated readiness, opened the actual Edit Profile form, and continued past restart and wallet-restoration verification. The test's final failure occurred later, during profile deletion.

    Test retry Restart marker → observed authenticated readiness
    1 67.158s
    2 66.542s
    3 67.612s

    These are test-observed elapsed times, including app startup, navigation and polling; they are not exact SDK initialization durations. The test records a missed 30-second fast path separately and continues a bounded recovery observation. A later readiness result does not meet the original 30-second expectation.

    Continuous native/app logs show repeated Deferred session restoration, keeping saved session with shared_state_busy in each restart observation. For retry 3, these occur from 04:48:43.874 to 04:49:24.810 UTC; events.jsonl records readiness at 04:49:39.178 UTC, 67.612s after the restart marker.

    All three retries then passed the profile details, copied public key and saved-contact assertions after wallet restoration, and reached the subsequent profile edit/deletion steps. This is bounded evidence for these fixtures, not full restore/payment acceptance.

    Separate later failure: all three retries timed out waiting for PubkyChoiceCreate after confirming Delete. The final screenshots show disabled Edit Profile; iOS shows a deletion spinner. Logs show private endpoint withdrawal still pending. Android reports Private Paykit is not available; iOS retries 2/3 report cannot disable Paykit app capability private_payments while app-owned work is still active. Contact cleanup completed in several retries, but complete profile deletion was not established within the observation window. This should not be attributed to this restart-recovery ticket without further investigation; eventual deletion completion and its cause remain unknown.

    Complete failed-shard artifact includes all three retries, events.jsonl, native logs, app-log snapshots and screenshots/recordings. Android retries 1/3 recordings had no decodable frames; their screenshots and logs were inspected. The Android retry-2 recording and iOS recordings were inspected through sampled frames.

    No new device run, fix, permanent-deadlock claim or release sign-off is made. This confirms continuing slow-recovery exposure on rc72 and supplies eventual recovery observations that the earlier shorter nightly capture did not establish.

  4. ben-kaufman commented on Oct 15, 2026

    @ben-kaufman
    Contributor

    Reproduced slow recovery on publishedRC77 / unchanged master 080118815, using an unfunded disposable62-contact staging identity. After terminate/relaunch, public Profile appeared at9.30s; authenticated editing recovered automatically at69.07s and the real Edit form opened. The30s fast path still fails. Six visible contact identities matched before/after; no payment was attempted.

    Native logs repeatedly reported SharedStateBusy. Six read-only marker-directory samples during the early recovery interval returned404/DirectoryNotFound: no pending marker observed, not proof of the exact lock holder or a continuous audit.

    An independent control on this signed-out disposable identity, using the exactRC77 stack, abandoned a normal60s read-only lock with no pending marker or state write. Normal bounded acquisition remained busy through57.75s and succeeded/released at60.76s. This confirms the read-lease mechanism still exists; it does not attribute every mobile/nightly delay to it. The five-minute uncertain-write wait is separate and unchanged.

    Deletion completed in40.47/41.77s after two import cycles and42.24s after restart. Contact removal was followed by about19-20s of profile/public cleanup and queueing before sign-out; the final revoke request itself took about0.2-0.3s. No permanent deletion failure reproduced. Original nightly captures ended before eventual completion was established.

    No production patch: no safe narrow change was justified by these results. No guard, stored-session handling, marker or lease was weakened. Recovery remains open; phase/holder attribution is still needed for further optimization.

  5. added this to the 3.1.0 milestone on Oct 15, 2026
  6. ben-kaufman commented on Oct 15, 2026

    @ben-kaufman
    Contributor

    Further operation-level tracing found a narrow app scheduling defect, now addressed in #1510: automatic contact/request/publication work could start while explicit profile deletion was already running. In one baseline trace, a nonempty background endpoint publication took 6.96s directly ahead of the ordered empty-endpoint cleanup; contact-triggered app publication added another 5.96s.

    The fix defers new automatic work during deletion and resumes it when deletion ends. Admitted operations, explicit cleanup, payment bookkeeping/proof completion, identity checks and all safety waits remain intact. Final native coverage: 4,246 tests passed; complete Detekt/compiler diagnostics were compared against the exact base with no introduced finding.

    The final APK completed two disposable 62-contact import/deletion cycles. First deletion reached onboarding in 38.5s including navigation/confirmation. After the second cycle's cold restart, public controls appeared in 9.7s and authenticated editing in 68.7s. A driver navigation error and then Appium attachment failure interrupted that test; deletion subsequently completed through the Android CLI on the same unfunded wallet within 55s of confirmation. The old profile stayed absent after reopening. No reset or payment.

    Recovery remains open: repeated bounded SharedStateBusy attempts and lease/retry timing still produce roughly one-minute waits. The original remote holder is not established, and no safety wait was reduced. These runs did not reproduce a permanent deletion failure or establish an overall speedup. Controlled cleanup-failure/resume device checks remain unchecked in the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions