Skip to content

fix(android): keep chat model selection synchronized#100985

Merged
steipete merged 5 commits into
mainfrom
codex/fix-android-model-picker
Jul 6, 2026
Merged

fix(android): keep chat model selection synchronized#100985
steipete merged 5 commits into
mainfrom
codex/fix-android-model-picker

Conversation

@steipete

@steipete steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Related: #100798
Related: #100699

What Problem This Solves

Fixes issues where Android users selecting a chat model could send on the previous model after a failed patch, see an accepted selection overwritten by stale history, or receive the default agent's model catalog while chatting with another agent. It also prevents a temporarily empty model catalog from remaining empty for the session.

Why This Change Was Made

The chat controller now owns the active agent's combined command/model metadata, treats model patch acceptance as the send-ordering boundary, hydrates the selected model from canonical session history, and rejects stale history model state after a newer local selection. Empty catalog metadata gets one bounded retry so timeout fallbacks recover without polling forever for a legitimately empty catalog.

User Impact

Android's in-chat model picker stays aligned with the active session and agent. Immediate sends wait for a successful model change; failed changes remain visible and do not send on the wrong model.

Evidence

  • Blacksmith Testbox tbx_01kwvyq0f7630vtfpjeeqwxt2d: focused Play-debug unit tests passed for model selection, command metadata, reconnect restoration, picker ordering, secure preferences, connection management, and bootstrap auth.
  • Regression coverage includes patch-failure send suppression, agent-scoped catalogs, history hydration, bounded empty-catalog retry, and stale-history ordering.
  • Fresh Codex autoreview: no accepted/actionable findings.
  • git diff --check: clean.
  • Full Android ktlint remains blocked by pre-existing findings in CronJobDetailTest.kt, InvokeErrorParserTest.kt, and NodeUtilsTest.kt; no touched file was reported.
  • Device UI proof gap: the connected Pixel remained keyguard-locked, so no screenshot or manual interaction proof was uploaded.

@openclaw-barnacle openclaw-barnacle Bot added app: android App: android size: M maintainer Maintainer-authored PR docs Improvements or additions to documentation app: web-ui App: web-ui gateway Gateway runtime size: XL and removed size: M labels Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 272e7ed98d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +529 to +533
if (chatMetadataAgentId != nextAgentId) {
_commands.value = emptyList()
commandsAgentId = null
_modelCatalog.value = emptyList()
chatMetadataAgentId = null
chatMetadataLoadState = ChatMetadataLoadState.Unloaded

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear chat metadata on gateway scope changes

When the user connects to a different Gateway that uses the same active agent id (the common main case), beginConnect() only calls chat.onGatewayScopeChanging() before replacing sockets, and GatewaySession.connect() closes the old socket without necessarily firing onDisconnected(). Because this branch leaves chatMetadataLoadState and _modelCatalog intact when the agent id matches, the next health/bootstrap path sees hasCurrentChatMetadata() as true and skips chat.metadata; since ChatScreen now reads chatModelCatalog, the model picker can keep showing the previous Gateway's models.

Useful? React with 👍 / 👎.

@steipete
steipete requested a review from a team as a code owner July 6, 2026 15:51
@openclaw-barnacle openclaw-barnacle Bot added the channel: discord Channel integration: discord label Jul 6, 2026
@steipete
steipete force-pushed the codex/fix-android-model-picker branch from 9edf39d to 12ef50b Compare July 6, 2026 15:52
@openclaw-barnacle openclaw-barnacle Bot added size: M and removed docs Improvements or additions to documentation channel: discord Channel integration: discord app: web-ui App: web-ui gateway Gateway runtime size: XL labels Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12ef50b74e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1124 to +1128
chatMetadataLoadState =
when {
models.isNotEmpty() -> ChatMetadataLoadState.Loaded
chatMetadataLoadState == ChatMetadataLoadState.RetryEmptyCatalog -> ChatMetadataLoadState.Loaded
else -> ChatMetadataLoadState.RetryEmptyCatalog

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the retry for a later metadata fetch

When the chat screen opens it calls both loadChat(...) and refreshChatCommands(), so fetchChatMetadata() can be in flight twice for the same agent before either response updates chatMetadataLoadState. If the model catalog read times out for both near-simultaneous chat.metadata calls, the first empty response moves the state to RetryEmptyCatalog and the second immediately marks it Loaded, so later health events skip the intended retry and the picker can stay empty for the session even after the catalog becomes available. Track the retry per completed fetch generation or serialize metadata loads so the second empty response is an actual later retry, not just a concurrent duplicate.

Useful? React with 👍 / 👎.

@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Land-ready review complete.

Fixes verified:

  • failed sessions.patch blocks the immediate send instead of using the prior model;
  • selected model hydrates from canonical session history and newer accepted selections win over stale in-flight history;
  • command/model metadata follows the active session agent;
  • empty catalog fallback retries once, then settles for a valid empty catalog.

Proof:

  • exact head 12ef50b74e7b553d179ef8f411a42916e69ce0d0;
  • CI run https://github.com/openclaw/openclaw/actions/runs/28804597098: all relevant jobs green, including native-i18n, android-test-play, android-test-third-party, android-build-play, QA Smoke CI, type/lint, and broad Node shards;
  • Blacksmith Testbox tbx_01kwvyq0f7630vtfpjeeqwxt2d: cd apps/android && ./gradlew :app:testPlayDebugUnitTest --tests 'ai.openclaw.app.chat.ChatControllerModelSelectionTest' --tests 'ai.openclaw.app.chat.ChatControllerCommandControlsTest' --tests 'ai.openclaw.app.chat.ChatControllerReconnectRestoreTest' --tests 'ai.openclaw.app.ui.chat.ChatModelPickerTest' --tests 'ai.openclaw.app.SecurePrefsTest' --tests 'ai.openclaw.app.node.ConnectionManagerTest' --tests 'ai.openclaw.app.GatewayBootstrapAuthTest' passed;
  • git diff --check passed;
  • fresh whole-branch autoreview: no accepted/actionable findings.

Known proof gap: the connected Pixel remained keyguard-locked, so no manual device interaction or screenshot proof was produced. No proof assets were committed or uploaded.

@steipete
steipete merged commit 255b763 into main Jul 6, 2026
88 of 92 checks passed
@steipete
steipete deleted the codex/fix-android-model-picker branch July 6, 2026 16:06
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 7, 2026
* fix(android): harden chat model selection

* fix(android): bound model metadata retries

* fix(android): preserve newer model selections

* chore: leave changelog to release workflow

* chore: sync native i18n inventory
giodl73-repo pushed a commit to giodl73-repo/openclaw that referenced this pull request Jul 8, 2026
* fix(android): harden chat model selection

* fix(android): bound model metadata retries

* fix(android): preserve newer model selections

* chore: leave changelog to release workflow

* chore: sync native i18n inventory
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant