Skip to content

fix(android): show specific gateway auth-recovery reason instead of generic label#98698

Merged
steipete merged 7 commits into
openclaw:mainfrom
masatohoshino:fix/android-gateway-auth-needed-detail
Jul 2, 2026
Merged

fix(android): show specific gateway auth-recovery reason instead of generic label#98698
steipete merged 7 commits into
openclaw:mainfrom
masatohoshino:fix/android-gateway-auth-needed-detail

Conversation

@masatohoshino

@masatohoshino masatohoshino commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Related #98046. During Android gateway setup/reconnect, gatewaySummary() (Overview screen /
Settings row) and gatewayStatusLabel() (Gateway settings metric) collapse every auth failure
into one generic "Authentication needed" label — whether the setup QR expired, a token/password
is missing or invalid, or the saved device identity is stale. Users cannot distinguish an expired
setup code from an invalid or stale saved credential in these two UI surfaces.

This covers only the Overview and Settings screens. #98094 (merged) / #98275 / #98280 already
cover the same issue's Onboarding/Recovery-screen copy (OnboardingFlow.kt) — a different
user-visible surface than this PR touches.

Why This Change Was Made

Client-only change — no gateway protocol, config, or wire-format change. MainViewModel. gatewayConnectionProblem: StateFlow<GatewayConnectionProblem?> already carries a machine-readable
code from the gateway's auth-reject response (AUTH_BOOTSTRAP_TOKEN_INVALID,
AUTH_TOKEN_MISSING, AUTH_PASSWORD_MISSING, AUTH_PASSWORD_MISMATCH,
AUTH_TOKEN_MISMATCH/AUTH_DEVICE_TOKEN_MISMATCH,
CONTROL_UI_DEVICE_IDENTITY_REQUIRED/DEVICE_IDENTITY_REQUIRED), already consumed by
recoveryGatewayAuthDetail() on the Onboarding/Recovery screen, but the Overview and Settings
screens never received it and fell back to a plain substring match on statusText. This threads
the existing signal into both functions via a shared gatewayAuthNeededSummary() helper with
short, code-specific labels, wiring viewModel.gatewayConnectionProblem into the three call sites
that already have viewModel in scope. The iOS app already solved the same problem with an
equivalent GatewayConnectionIssue enum
(apps/ios/Sources/Gateway/GatewayConnectionIssue.swift) derived from the same problem shape.

User Impact

  • Overview screen, Settings "Gateway" row, and Settings → Gateway → Status metric now show
    specific recovery guidance (e.g. "Setup code expired", "Gateway token needed", "Saved auth
    invalid", "Device identity required") instead of a generic "Authentication needed" whenever the
    gateway has already reported why auth failed.
  • Falls back to the previous generic "Authentication needed" label when no recognized problem
    code is present. Non-auth states (connecting, pairing, TLS, certificate) are unaffected.

Evidence

Behavior proof (real device)

Set up an isolated Android emulator (API 31, aosp_atd x86_64 image, software-rendered — no
hardware acceleration available in this environment) plus a real, isolated OpenClaw gateway
instance (fresh state, throwaway auth token, destroyed after this proof). Built and installed
this branch's thirdPartyDebug APK, paired the emulator with the real gateway end-to-end (real
WebSocket handshake, real token auth, real device approval via nodes approve, real
node-capability approval), then removed the paired-device entry server-side
(devices remove <id>) to force a genuine device_token_mismatch rejection on reconnect — the
real-world equivalent of a stale/revoked saved credential.

Gateway-side log for the real rejection:

unauthorized client=Android SDK built for x86_64 node ... auth=token device=yes platform=android
  ... reason=device_token_mismatch
unauthorized client=Android SDK built for x86_64 ui ... auth=token device=yes platform=android
  ... reason=device_token_mismatch

device_token_mismatch is the gateway's auth-reject reason string; the gateway maps it to the
structured detail code via resolveAuthConnectErrorDetailCode
(packages/gateway-protocol/src/connect-error-details.js) before sending it, which is what
populates GatewayConnectionProblem.code = "AUTH_DEVICE_TOKEN_MISMATCH" on the Android side.

Resulting real app state, captured via uiautomator dump (accessibility-tree snapshot of the
actual running Compose UI) — screenshot capture in this software-rendered emulator session
produced only a black frame, so the UI-tree dump is the retained visual evidence:

  • Overview screen (OverviewPrimaryPanel, driven by gatewaySummary()): agent row shows
    "Saved auth invalid" under the agent name (previously this and the Settings label below
    would both have shown the pre-fix generic "Authentication needed"), and a
    "Reconnect gateway" action appears.
  • Settings → Gateway detail (GatewaySettingsScreen, driven by gatewayStatusLabel()):
    Status: "Saved auth invalid". The same session confirmed Connection: Connected /
    Status: Ready moments earlier while still paired, before the forced rejection.

This exercises the entire real path this PR changes: real gateway auth rejection ->
NodeRuntime.handleGatewayConnectFailure -> real GatewayConnectionProblem(code = "AUTH_DEVICE_TOKEN_MISMATCH") -> real gatewaySummary()/gatewayStatusLabel() -> real Compose
recomposition -> real rendered label, with no code under test mocked or stubbed.

Behavior proof (compiled-function invocation, all 6 codes)

The device proof above covers one code end-to-end; JVM invocation of the actual compiled
ShellScreen.kt/SettingsScreens.kt production functions covers all 6 codes with the exact real
gateway auth-reject message text, as precedent-following supplementary coverage:

[Expired bootstrap/setup QR] code=AUTH_BOOTSTRAP_TOKEN_INVALID
  real gateway statusText = "Gateway error: unauthorized: bootstrap token invalid or expired (scan a fresh setup code)"
  Overview label (gatewaySummary)      -> "Setup code expired"
  Settings label (gatewayStatusLabel)  -> "Setup code expired"

[Missing gateway token] code=AUTH_TOKEN_MISSING
  real gateway statusText = "Gateway error: unauthorized: gateway token missing (set gateway.remote.token to match gateway.auth.token)"
  Overview label (gatewaySummary)      -> "Gateway token needed"
  Settings label (gatewayStatusLabel)  -> "Gateway token needed"

[Gateway password mismatch] code=AUTH_PASSWORD_MISMATCH
  real gateway statusText = "Gateway error: unauthorized: gateway password mismatch (enter the password in Control UI settings)"
  Overview label (gatewaySummary)      -> "Gateway password invalid"
  Settings label (gatewayStatusLabel)  -> "Gateway password invalid"

[Stale saved device token] code=AUTH_DEVICE_TOKEN_MISMATCH
  real gateway statusText = "Gateway error: unauthorized: device token mismatch (rotate/reissue device token)"
  Overview label (gatewaySummary)      -> "Saved auth invalid"
  Settings label (gatewayStatusLabel)  -> "Saved auth invalid"

[Device identity required (Control UI, insecure origin)] code=CONTROL_UI_DEVICE_IDENTITY_REQUIRED
  real gateway statusText = "Gateway error: control ui requires device identity (use https or localhost secure context)"
  Overview label (gatewaySummary)      -> "Device identity required"
  Settings label (gatewayStatusLabel)  -> "Device identity required"

[Device identity required (paired-device handshake)] code=DEVICE_IDENTITY_REQUIRED
  real gateway statusText = "Gateway error: device identity required"
  Overview label (gatewaySummary)      -> "Device identity required"
  Settings label (gatewayStatusLabel)  -> "Device identity required"

=== Fallback preserved when no recognized problem code is present ===
  gatewaySummary("auth failed", false, null) -> "Authentication needed"

Unit tests

gatewaySummary() and gatewayStatusLabel() were promoted from private to internal (this
file's existing pattern for JVM-unit-tested logic, e.g. overviewHeaderState), with new
deterministic tests per auth code, a null/unrecognized-code fallback, unrelated-state
non-interference, and two tests per file using the exact real server message text (src/gateway/ server/ws-connection/auth-messages.ts and message-handler.ts) rather than only synthetic
strings.

Rebased on upstream/main @ adcfebc276:

$ ./gradlew :app:testThirdPartyDebugUnitTest \
    --tests "ai.openclaw.app.ui.ShellScreenLogicTest" \
    --tests "ai.openclaw.app.ui.SettingsScreensTest" --rerun-tasks

BUILD SUCCESSFUL in 12s
ShellScreenLogicTest: tests=25 failures=0 errors=0 skipped=0
SettingsScreensTest:  tests=6  failures=0 errors=0 skipped=0

Lint

./gradlew :app:ktlintCheck — the touched files (ShellScreen.kt, SettingsScreens.kt,
ShellScreenLogicTest.kt, SettingsScreensTest.kt) are clean. 5 pre-existing violations remain
in three unrelated files (GatewayExecApprovals.kt, ClawSurfaces.kt, OnboardingFlow.kt), none
touched by this diff.

i18n

pnpm native:i18n:sync run and committed (apps/.i18n/native-source.json) — the auth-recovery
wiring shifted line numbers for existing catalogued strings in the same two files;
pnpm native:i18n:check passes clean after the sync.

Review

autoreview (Codex, --mode branch --base upstream/main): clean, no accepted/actionable
findings, overall: patch is correct (0.86), re-run after the i18n sync commit.

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 2, 2026, 8:37 AM ET / 12:37 UTC.

Summary
The PR centralizes Android gateway auth-recovery labels, publishes correlated GatewayConnectionDisplay state, wires Overview/Settings and adjacent Android UI to it, updates focused tests, and syncs native i18n metadata.

PR surface: Other +409. Total +409 across 17 files.

Reproducibility: yes. Current main source shows Overview and Settings collapsing auth-looking gateway status text to generic labels while structured gateway problem codes already exist, and the PR body supplies after-fix emulator/gateway proof for the changed labels.

Review metrics: 1 noteworthy metric.

  • Issue-Closing Syntax: 1 commit message contains closing syntax. The referenced Android auth-recovery issue is broader than this PR, so merge text can change issue state even when the code is correct.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #98046
Summary: This PR is a candidate fix for the Overview and Settings status-label subset of the broader Android gateway auth-recovery issue.

Members:

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

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
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:

  • Use edited or squashed non-closing merge text so the broader Android auth-recovery issue stays open.

Risk before merge

Maintainer options:

  1. Use Non-Closing Merge Text (recommended)
    Before merge, replace the first commit's closing reference with a non-closing reference to Android setup "authentication needed" state should distinguish stale credentials, pending approval, and QR/bootstrap expiry #98046 so the broader issue remains open.
  2. Accept Manual Issue Cleanup
    Maintainers may preserve the commit history only if they intentionally monitor and reopen or replace the broader Android auth-recovery issue after merge.

Next step before merge

  • [P2] Human merge handling is the remaining blocker; there is no narrow code repair to queue.

Security
Cleared: The diff touches Android UI/runtime display logic, focused tests, and generated native i18n metadata; no concrete security or supply-chain regression was found.

Review details

Best possible solution:

Land the code with edited or squashed non-closing merge text, and keep #98046 open for the remaining Android setup-recovery work.

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

Yes. Current main source shows Overview and Settings collapsing auth-looking gateway status text to generic labels while structured gateway problem codes already exist, and the PR body supplies after-fix emulator/gateway proof for the changed labels.

Is this the best way to solve the issue?

Yes. Centralizing the structured auth label mapping and publishing correlated display/problem state is the maintainable fix; separately collecting raw status and problem flows at each UI surface would keep the stale-state mismatch risk.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 3ae5e98bf605.

Label changes

Label justifications:

  • P2: This is a bounded Android gateway-auth recovery UI fix with limited blast radius and sufficient proof, plus one merge-text risk to handle before landing.
  • merge-risk: 🚨 other: Merging with the current commit body could close the broader Android auth-recovery issue before this narrower PR resolves all remaining work.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster 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 includes after-fix real Android emulator proof against a real gateway auth rejection, with gateway logs and UIAutomator output showing the changed Overview and Gateway Settings labels.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-fix real Android emulator proof against a real gateway auth rejection, with gateway logs and UIAutomator output showing the changed Overview and Gateway Settings labels.
Evidence reviewed

PR surface:

Other +409. Total +409 across 17 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 17 773 364 +409
Total 17 773 364 +409

What I checked:

Likely related people:

  • vincentkoc: Current-main blame on the generic Android status helpers and GatewayConnectionProblem/runtime status lines points to the grafted current-main Android snapshot attributed to Vincent Koc; the checkout history is shallow/grafted, so this is useful but not complete provenance. (role: current-main blamed Android status/runtime contributor; confidence: medium; commits: 1fcce9f1ee99; files: apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt, apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt, apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt)
  • joshavant: GitHub path history shows recent NodeRuntime/GatewaySession work on mobile protocol mismatch recovery, TLS timeout handling, saved-gateway reconnect, and pairing retry behavior adjacent to this status path. (role: recent Android gateway recovery contributor; confidence: high; commits: ad59492d3ca9, 21d1e1f0fc9d, 81f4fe6c113b; 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/ui/OnboardingFlow.kt)
  • obviyus: GitHub path history shows earlier Android gateway pairing, private-LAN credential trust, reconnect, and setup flow work that established much of the gateway-session behavior this PR renders. (role: Android gateway/session history contributor; confidence: medium; commits: 771ddcf1846f, 0b55a6363e2d, f1f92b8656a8; files: apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewaySession.kt, apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt, apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt)
  • qingminglong: The merged related PR implemented the Onboarding/Recovery structured auth copy for the same broader issue, making it directly adjacent to this PR's Overview/Settings status-label subset. (role: adjacent merged auth-recovery PR author; confidence: medium; commits: 0d275c8c9d39; files: apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt, apps/android/app/src/test/java/ai/openclaw/app/ui/OnboardingFlowLogicTest.kt)
  • steipete: GitHub history shows gateway protocol/auth compatibility work, and the live PR timeline shows Peter Steinberger force-pushed the maintainer rewrite that centralized display correlation on this branch. (role: gateway auth boundary contributor and PR branch rewriter; confidence: high; commits: 3f815fad1293, bf1be9f22470, 66b2fd0a293a; files: packages/gateway-protocol/src/connect-error-details.ts, apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt, apps/android/app/src/main/java/ai/openclaw/app/ui/GatewayDiagnostics.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. P2 Normal backlog priority with limited blast radius. labels Jul 1, 2026
@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 self-assigned this Jul 2, 2026
@steipete
steipete requested a review from a team as a code owner July 2, 2026 11:04
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Security-sensitive guard cleared

This PR no longer has blocked security-sensitive file changes. A future security-sensitive change requires a fresh /allow-security-sensitive-change comment after the guard blocks that new head SHA.

  • Current SHA: 5c23a86e48d2e9305330469257dc459b178a78d0

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: discord Channel integration: discord channel: imessage Channel integration: imessage channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: nostr Channel integration: nostr channel: telegram Channel integration: telegram app: ios App: ios app: macos App: macos app: web-ui App: web-ui gateway Gateway runtime extensions: memory-core Extension: memory-core cli CLI command changes labels Jul 2, 2026
@steipete

steipete commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Land-ready maintainer proof

Exact head: 5c23a86e48d2e9305330469257dc459b178a78d0

Maintainer rewrite keeps connection status, connectivity, and diagnostics atomic and structured:

  • GatewayConnectionDisplay is the single UI snapshot; consumers no longer combine independently-timed flows.
  • GatewayConnectionProblem carries auth, bootstrap-expiry, approval, and TLS/trust states without parsing display text.
  • Retryable pairing guidance survives only the matching Reconnecting transition, then clears on success, disconnect, or an unrelated failure.
  • One short-label mapper owns compact UI copy; status-only TLS/trust updates preserve live connectivity.

Validation:

  • GatewayBootstrapAuthTest, GatewayConnectionDisplayTest, and OnboardingFlowLogicTest: pass.
  • Android ktlint, native i18n generation/check, and git diff --check: pass.
  • Full third-party Android unit lane: 644 tests passed on the prior exact implementation tree; the final rebase was conflict-free.
  • Fresh exact-tree Codex autoreview: no actionable findings, patch correct, confidence 0.90.
  • Hosted exact-head checks: 61 passed, 13 intentionally skipped, no failures.

The contributor's emulator before/after evidence remains valid for the user-visible label change. Broader multi-cause recovery work remains tracked by #98046.

masatohoshino and others added 7 commits July 2, 2026 14:39
…e generic label

gatewaySummary()/gatewayStatusLabel() collapsed every auth failure (expired
setup QR, missing/invalid token, missing/invalid password, stale device
identity) into "Authentication needed", discarding the already-computed
MainViewModel.gatewayConnectionProblem signal consumed elsewhere in the app
(OnboardingFlow's recoveryGatewayAuthDetail). Thread it into both status
labels via a shared gatewayAuthNeededSummary() helper so the Overview and
Settings screens tell the user which recovery action applies.

Fixes openclaw#98046
…ed message format

Verified against src/gateway/server/ws-connection/auth-messages.ts:
formatGatewayAuthFailureMessage always returns strings starting with
"unauthorized", which contains the "auth" substring the status-text gate
checks for. Add tests using the exact real-world message text (not just
synthetic "auth failed" strings) so this stays regression-locked.
…ity failures

CONTROL_UI_DEVICE_IDENTITY_REQUIRED/DEVICE_IDENTITY_REQUIRED real gateway
messages ("device identity required", "control ui requires device
identity...") don't contain "auth", so the status.contains("auth") gate
never reached their specific label. Add "device identity" as an additional
substring the gate checks for those two codes, with regression tests using
the exact real message text (message-handler.ts).

Verified via GitHub-style codex review round 2 (round 1's "unauthorized
doesn't contain auth" claim was checked against source and rejected as
false — "unauthorized" does contain "auth" — but this second, narrower
finding about device-identity codes held up under the same source check).
@steipete

steipete commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

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: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XL status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants