Skip to content

fix(android): queue node events until gateway connect#92602

Merged
steipete merged 4 commits into
openclaw:mainfrom
ashishpatel26:fix/79552-android-node-event-handshake
Jul 2, 2026
Merged

fix(android): queue node events until gateway connect#92602
steipete merged 4 commits into
openclaw:mainfrom
ashishpatel26:fix/79552-android-node-event-handshake

Conversation

@ashishpatel26

@ashishpatel26 ashishpatel26 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #79552.

What Problem This Solves

Android notification forwarding could call node.event while the WebSocket existed but the Gateway handshake was still incomplete. The Gateway requires connect to be the first request, so notification-triggered automations could silently miss events during initial connect and reconnect windows.

Why This Change Was Made

  • Make each GatewaySession.Connection own its pending RPCs and explicit connecting/ready/closed state; outward RPCs see only a ready connection.
  • Queue notification events in one bounded FIFO and wake it only after a completed handshake.
  • Retain only events proven not enqueued to OkHttp. Ambiguous post-enqueue failures are dropped to prevent duplicates, while still consuming their configured rate-limit slot.
  • Recheck listener access, package policy, quiet hours, and session routing at delivery time.
  • Coordinate notification-policy writes with outbox generation rollover so restrictive changes become visible before another RPC can be admitted.
  • Close failed/cancelled connecting sockets before retrying.

This replaces the original reconciliation/parallel-ID approach; it does not replay the device's entire active-notification set after reconnect.

User Impact

Notifications accepted while Android is disconnected or still handshaking are delivered in order after reconnect. The queue is bounded, policy changes purge stale content, and uncertain sends are not replayed as duplicates.

Evidence

  • API 36.1 Android emulator + real local Gateway: stopped the Gateway, waited until the node connection was closed, posted a unique shell notification, then restarted the Gateway. The node completed its handshake at 22:52:10, the Gateway received exactly one node.event, returned success in 186 ms, and no duplicate followed.
  • Focused Play and third-party unit suites passed for GatewaySessionInvokeTest, GatewaySessionReconnectTest, NotificationNodeEventOutboxTest, and GatewayBootstrapAuthTest.
  • :app:ktlintMainSourceSetCheck, :app:ktlintTestSourceSetCheck, and :app:assemblePlayDebug passed.
  • node --import tsx scripts/native-app-i18n.ts check passed.
  • Blacksmith Testbox tbx_01kwjdyehj5p35mb94ercj82x2: exact-head pnpm check:changed passed.
  • Fresh Codex autoreview: no accepted/actionable findings.

No screenshot is attached because this is a transport lifecycle fix with no meaningful visual before/after state; the real Gateway transcript is the behavioral proof.

@openclaw-barnacle openclaw-barnacle Bot added app: android App: android size: M triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 13, 2026
@clawsweeper

clawsweeper Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 2, 2026, 6:33 PM ET / 22:33 UTC.

Summary
The PR gates Android GatewaySession RPCs on a ready connection and adds a bounded notification node-event outbox with reconnect/policy tests and native i18n inventory refresh.

PR surface: Other +811. Total +811 across 7 files.

Reproducibility: yes. at source level: current main publishes currentConnection before the connect RPC finishes, Android notification forwarding sends through that readiness gate without retry, and the gateway rejects non-connect first requests. I did not run a live Android device or emulator in this read-only review.

Review metrics: 2 noteworthy metrics.

  • Queued notification cap: 128 pending events added. This defines the new overload and drop behavior for Android notifications accepted while the gateway is disconnected or handshaking.
  • Active replay production scope: 0 listener/store production files changed. The branch recovers events captured by the NodeRuntime sink rather than replaying the DeviceNotificationStore active snapshot.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #79552
Summary: The linked issue is the canonical Android notification handshake/message-loss report; this PR is the current open fix candidate, while earlier same-topic PRs are closed unmerged.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] The PR recovers notification events that enter the new NodeRuntime outbox; it does not replay the full DeviceNotificationStore active-notification snapshot requested in the linked issue text.
  • [P1] The bounded FIFO and ambiguous-send handling intentionally trade completeness for duplicate avoidance: buffered notifications can be dropped at 128 pending entries, and post-enqueue failures are not retried.

Maintainer options:

  1. Accept the scoped queued-event fix (recommended)
    If maintainers agree full active-snapshot replay is out of scope, land after normal exact-head gates and update the linked issue accordingly.
  2. Add active replay before merge
    If the linked bug must include notifications posted while no sink event was captured, add active-notification replay and delivered-key dedupe before landing.
  3. Split the replay question
    Maintainers can land this handshake/outbox repair and keep a separate canonical issue for active snapshot replay if that behavior is still desired.

Next step before merge

  • [P2] Maintainer review is needed to accept the scoped delivery semantics and proceed with landing; no concrete automated code repair remains.

Security
Cleared: The diff is limited to Android Kotlin runtime/tests and native i18n inventory, with no dependency, workflow, secret, permission, lockfile, package, or persistent schema changes.

Review details

Best possible solution:

Land this PR if maintainers accept scoped queued-event recovery and bounded duplicate-avoidance semantics; otherwise keep or split a narrower follow-up for active-notification snapshot replay.

Do we have a high-confidence way to reproduce the issue?

Yes, at source level: current main publishes currentConnection before the connect RPC finishes, Android notification forwarding sends through that readiness gate without retry, and the gateway rejects non-connect first requests. I did not run a live Android device or emulator in this read-only review.

Is this the best way to solve the issue?

Yes for the queued-node-event race: making GatewaySession expose only ready connections and letting NodeRuntime own bounded notification delivery is the right owner boundary. Full active-notification replay remains a maintainer scope decision rather than a definite patch defect.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 32762dad539d.

Label changes

Label justifications:

  • P1: The PR targets Android notification event loss during gateway reconnect, which can break real notification-triggered workflows.
  • merge-risk: 🚨 message-delivery: The diff changes when Android notification events are queued, dropped, retried, or delivered after reconnect.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The PR body and maintainer comment report an API 36.1 emulator with a real local Gateway reconnect run where one queued node.event delivered after handshake and no duplicate followed, which is sufficient non-visual transport proof.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body and maintainer comment report an API 36.1 emulator with a real local Gateway reconnect run where one queued node.event delivered after handshake and no duplicate followed, which is sufficient non-visual transport proof.
Evidence reviewed

PR surface:

Other +811. Total +811 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 7 868 57 +811
Total 7 868 57 +811

What I checked:

Likely related people:

  • steipete: Assigned on this PR, force-pushed the current maintainer rewrite, authored the current PR commits, and has adjacent Android gateway auth/connect refactor history. (role: recent PR steward and adjacent gateway contributor; confidence: high; commits: 679a878f6d60, 129ef0b33953, 589aca0e6d73; files: apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt, apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt)
  • Ayaan Zaidi: Main-branch history shows central Android GatewaySession and NodeRuntime work around auth, reconnect, setup, and runtime behavior in this area. (role: major Android gateway/runtime contributor; confidence: high; commits: dcf821cfb62b, 11bd40fe8a0c, 5568b393a86d; files: apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt, apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt)
  • nimbleenigma: The Android notification-forwarding controls PR touched NodeRuntime, GatewaySession, and DeviceNotificationListenerService, the same delivery surfaces involved here. (role: notification forwarding feature-history contributor; confidence: medium; commits: aee61dcee0f3; files: apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt, apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt, apps/android/app/src/main/java/ai/openclaw/app/node/DeviceNotificationListenerService.kt)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🌊 off-meta tidepool PR readiness rating does not apply to this item. labels Jun 13, 2026
@ashishpatel26

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

Branch merged with upstream/main (clean, no conflicts). Fixed deprecated StatusBarNotification 10-arg constructor — removed the score parameter (deprecated since API 21). Both Android unit-test variants should now compile and run.

@clawsweeper

clawsweeper Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@steipete steipete self-assigned this Jul 2, 2026
@steipete
steipete force-pushed the fix/79552-android-node-event-handshake branch from 27f60c3 to da4554e Compare July 2, 2026 22:06
@steipete
steipete force-pushed the fix/79552-android-node-event-handshake branch from da4554e to 2229c4d Compare July 2, 2026 22:10
@steipete

steipete commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Land-ready maintainer rewrite at 2229c4d27cbb373543351e7e20cc67c5164dee05.

What changed:

  • GatewaySession.Connection now owns connecting/ready/closed state and its pending RPCs; outward calls cannot use a pre-handshake socket.
  • A bounded notification outbox preserves order and retains only definitely-unsent events. Ambiguous sends are not replayed, delivery is paced, and policy/listener authorization is rechecked before admission.
  • Restrictive policy writes and outbox generation rollover are atomic at the admission boundary; stale in-flight sends force a reconnect.

Verification:

  • ./gradlew :app:testPlayDebugUnitTest :app:testThirdPartyDebugUnitTest --tests ai.openclaw.app.gateway.GatewaySessionInvokeTest --tests ai.openclaw.app.gateway.GatewaySessionReconnectTest --tests ai.openclaw.app.NotificationNodeEventOutboxTest --tests ai.openclaw.app.GatewayBootstrapAuthTest
  • ./gradlew :app:ktlintMainSourceSetCheck :app:ktlintTestSourceSetCheck :app:assemblePlayDebug
  • node --import tsx scripts/native-app-i18n.ts check
  • Blacksmith Testbox tbx_01kwjdyehj5p35mb94ercj82x2: exact-head changed gate passed.
  • Hosted CI run 28624760489: native-i18n, android-test-play, android-test-third-party, and android-build-play all passed.
  • API 36.1 emulator + real Gateway: notification posted after confirmed disconnect; after restart, node handshake completed, exactly one node.event arrived and succeeded, with no duplicate.
  • Fresh Codex autoreview: clean after fixes for definite pre-enqueue disconnect classification, ambiguous-send pacing, and policy/admission ordering.

No before/after screenshot: the failure is transport ordering with no meaningful visual state; the emulator/Gateway transcript is the relevant proof.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 2, 2026
@steipete
steipete force-pushed the fix/79552-android-node-event-handshake branch from 2229c4d to 129ef0b Compare July 2, 2026 22:24
@steipete
steipete merged commit 8229f62 into openclaw:main Jul 2, 2026
74 checks passed
@steipete

steipete commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 3, 2026
* fix(android): queue notification events until gateway ready

Co-authored-by: Ashish Patel <[email protected]>

* style(android): format notification gateway changes

* style(android): fix gateway resolver formatting

* chore(android): sync native i18n inventory

---------

Co-authored-by: Peter Steinberger <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P1 High-priority user-facing bug, regression, or broken workflow. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: L status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Android node sends node.event before websocket connect handshake completes, causing notification events to be lost

2 participants