fix(android): queue notification events until node connect#91667
fix(android): queue notification events until node connect#91667mushuiyu886 wants to merge 1 commit into
Conversation
|
Codex review: needs real behavior proof before merge. Reviewed June 9, 2026, 4:53 AM ET / 08:53 UTC. Summary PR surface: Other +361. Total +361 across 5 files. Reproducibility: yes. at source level: current main publishes Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land this Android lifecycle fix after adding redacted real Android app plus OpenClaw gateway reconnect proof, while keeping the focused source-level regressions. Do we have a high-confidence way to reproduce the issue? Yes at source level: current main publishes Is this the best way to solve the issue? Yes, the proposed source shape appears to be the right owner-boundary fix: keep pending handshakes out of AGENTS.md: found and applied where relevant. Codex review notes: model gpt-5.5, reasoning high; reviewed against 257b251e2690. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Other +361. Total +361 across 5 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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
|
|
Closing this PR because ClawSweeper requires real Android app/emulator plus real gateway proof, and I cannot provide that proof from the current local environment. |
Summary
node.eventcannot be sent before theconnectRPC has succeeded.notifications.changedevents and flushes it after node reconnect.Real behavior proof
Behavior or issue addressed: Android notification forwarding could emit
notifications.changedasnode.eventwhile the WebSocketconnectRPC was still pending, making the gateway reject the session because the first accepted request was not the completed connect handshake. Reconnect windows could also drop notification events because failed sends were not queued and active notifications were not replayed.Why it matters / User impact: Android users could miss notification forwarding events after app start or reconnect, and the node session could enter an invalid handshake state instead of recovering cleanly.
Real environment tested: Local Linux Android JVM/Robolectric environment using the production Android
GatewaySession,NodeRuntime, andDeviceNotificationListenerServicecode paths with OkHttp WebSocket + MockWebServer gateway.ANDROID_HOME=$HOME/.android-sdk;JAVA_HOME=$HOME/.jdks/jdk-21.0.7+6.Exact steps or command run after this patch: From
apps/android:ANDROID_HOME="$HOME/.android-sdk" ANDROID_SDK_ROOT="$HOME/.android-sdk" JAVA_HOME="$HOME/.jdks/jdk-21.0.7+6" ./gradlew --no-daemon :app:testPlayDebugUnitTest --tests ai.openclaw.app.gateway.GatewaySessionInvokeTest --tests ai.openclaw.app.node.DeviceNotificationListenerServiceTest --rerun-tasksEvidence after fix: Evidence log:
openclaw-issue-79552-evidence/android-focused-tests-after-commit.logObserved result after fix: The focused JVM run exercised the production Android WebSocket session and notification listener paths. The new gateway regression verifies
sendNodeEvent()returns false while the connect response is pending and the server only observesconnect; the companion regression verifiesonConnectedcan immediately sendnotifications.changedafter connect success. The notification regressions verify bounded queue behavior, failed flush re-prepending, active-notification replay, retry when the sink is unavailable, and no duplicate replay of delivered keys.What was not tested: No physical Android device, OS notification shade, or external OpenClaw gateway was used; the proof is limited to local Android JVM/Robolectric plus local WebSocket gateway behavior.
Fix classification: Root cause fix.
Review findings addressed
GatewaySessionInvokeTest, focusedDeviceNotificationListenerServiceTest, andgit diff --check.Regression Test Plan
Target test file:
apps/android/app/src/test/java/ai/openclaw/app/gateway/GatewaySessionInvokeTest.ktandapps/android/app/src/test/java/ai/openclaw/app/node/DeviceNotificationListenerServiceTest.kt.Scenario locked in: The regression tests lock the exact lifecycle scenario: a WebSocket is open, the gateway has received the
connectrequest, the connect response is still pending, and Android notification forwarding attempts to sendnotifications.changed. They also lock the reconnect scenario where queued notification events flush after connect and active posted notifications replay only when their keys have not already emitted a posted event.Why this is the smallest reliable guardrail: The guardrail lives at the Android node/gateway boundary that was broken. It checks the real
GatewaySession.sendNodeEvent()result and the notification replay state instead of asserting implementation details, so future refactors can change internals without reintroducing pre-handshakenode.eventdelivery or duplicate active-notification replay.Android focused JVM tests:
Result:
BUILD SUCCESSFUL in 3m 13s; evidence:openclaw-issue-79552-evidence/android-focused-tests-after-commit.log.Android ktlint:
Result:
BUILD SUCCESSFUL in 22s; evidence:openclaw-issue-79552-evidence/android-ktlint-after-commit.log.Whitespace check:
Result: passed with no output; evidence:
openclaw-issue-79552-evidence/git-diff-check-after-commit.log.Merge risk
merge-risk: 🚨 message-delivery,merge-risk: 🚨 availability,merge-risk: 🚨 session-state.node.eventpayload schema, notification action APIs, Android forwarding policy defaults, provider/auth token selection,agent.request, and presence beacon behavior remain unchanged. The patch only changes when the Android node exposes a ready connection and hownotifications.changedrecovers during reconnect.GatewaySessionconnection lifecycle state plusDeviceNotificationStoreactive-notification state. This does not add a new public contract, schema, default, migration, or partial override; it restores the existing protocol invariant that the first gateway RPC for a socket isconnect. Related open PR scan for issue [Bug]: Android node sends node.event before websocket connect handshake completes, causing notification events to be lost #79552 found no linked open PRs inopenclaw-issue-79552-evidence/linked-open-prs-for-issue.json.notifications.changed, keepsagent.requestand presence beacon failure semantics unchanged, bounds queued notifications to 128 entries, and only promotes a gateway connection when the completed connect RPC still belongs to the current desired target.connectingConnectionguard prevents stale handshakes from being promoted, the notificationfalsereturns distinguish unavailable sinks from successful emits, and the queue lock is a process-local monitor for the bounded pending notification list rather than a type escape across APIs.Root Cause
currentConnectionas both the handshake-stage socket and the ready node socket. That violated the protocol invariant that the first usable gateway RPC on a socket is the successfulconnectRPC, so notification forwarding could treat a not-yet-connected socket as ready and sendnode.eventduring the handshake window. Failednotifications.changedsends were also fire-and-forget, and reconnect did not use active notification state as the source for replaying posted notifications missed while the node connection was unavailable.connectingConnectionrepresents a socket whoseconnectRPC is still pending, whilecurrentConnectionbecomes visible only after connect success and beforeonConnectedcallbacks run. Notification recovery is tied to the same ready-state transition by queueing only failednotifications.changedevents, flushing them after reconnect, and reconciling active notification entries that have not already emitted a posted event. This fixes the invalid first-request source path and the missed-notification recovery path without masking the symptom in downstream gateway error handling or changing unrelated node events.