Skip to content

fix(connection): keep device tokens when a gateway edit does not change the shared token - #1485

Merged
shanselman merged 2 commits into
openclaw:mainfrom
SebTardif:fix/f021-unchanged-shared-token
Sep 24, 2026
Merged

shanselman merged 2 commits into
openclaw:mainfrom
SebTardif:fix/f021-unchanged-shared-token

Conversation

@SebTardif

@SebTardif SebTardif commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

What Problem

Editing a saved gateway prefills the shared token. Save and connect sent that token and did not set PreserveExistingSharedTokenWhenMissing. A non-empty shared token cleared the operator and node device tokens and nulled the bootstrap token.

Why

The form showed the existing token as the current value. Saving without a change dropped a paired gateway back to the shared token.

User Impact

An unchanged shared token now connects with a null token and PreserveExistingSharedTokenWhenMissing true. The stored bootstrap token and device tokens stay.

This preservation applies only within the same credential realm. A changed endpoint or SSH credential endpoint still submits the token and resets authentication appropriately. Bootstrap preservation is at the save boundary; the existing BootstrapTokenLifecycle may subsequently retire a bootstrap token once both role tokens are durable.

Evidence

Red: BuildCandidate_UnchangedSharedToken_KeepsStoredBootstrapToken expected bootstrap-token and got null. Green: GatewayDirectConnectServiceTests and AppRefactorContractTests 107 passed. .\build.ps1 passed.

The preceding counts are the author's original evidence. Maintainer behavior proof below targets exact PR head 6000006cccde99ccc848b047b9688acbb6532303. A separate local-only prospective-main floor is recorded under Validation. Existing current-head dual review was reused. Production source was not changed for validation. ConnectionPage supplies the form decision; GatewayDirectConnectService remains the commit/rollback owner; GatewayConnectionManager owns connection lifecycle and device-token precedence.

Required proof pools

  • windows-winui-interactive: Verified by a real native Windows UI test on 2026-09-24. One fresh isolated app exercised the actual Save button for a friendly-name-only edit and a subsequent unchanged Save, with visible UIA state, native Gateway authentication and private credential comparisons after both operations.

Explicit maintainer selection calibration: this change preserves credentials when saving an existing gateway record. It does not change WSL provisioning, service management, shell commands, wire protocol or MXC. The full WSL SetupAndConnect class exercises setup, service restart, QR flows and MCP shared-token connection, not this Connection-page form/token comparison. For this protocol-agnostic boundary, the selected proof is the actual native Gateway behavior below plus windows-winui-interactive. windows-wsl-gateway-e2e provisioning was NOT RUN and is not selected for this scope. No WSL pool pass is claimed, and synthetic/MCP-only evidence is not being substituted for real Gateway authentication.

Validation

Native Windows ARM64, private .NET SDK 10.0.400, isolated worktree and tray data. OPENCLAW_REPO_ROOT pointed to the worktree. Unit-test processes cleared OPENCLAW_STATE_DIR, OPENCLAW_TRAY_LOCALAPPDATA_DIR, OPENCLAW_TRAY_LOCAL_DATA_DIR, fixture-mode and E2E flags. Test projects were built first to prevent fresh-worktree no-op runs. Test commands used counted TRX output.

Original PR head and behavior-proof tooling

Command / selector Passed Failed Skipped
.\build.ps1 at 6000006c Full build succeeded 0 n/a
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore 4090 0 35
dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore 3067 0 0
dotnet test .\tests\OpenClaw.Connection.Tests\OpenClaw.Connection.Tests.csproj --no-restore 789 0 5
Tray tests, --filter "FullyQualifiedName~GatewayDirectConnectServiceTests" 15 0 0
Integration tests, --filter "FullyQualifiedName~GatewayFixtureProfileTests|FullyQualifiedName~GatewayFixtureRunTests" 29 0 0
UI tests, --filter "FullyQualifiedName~GatewayFixtureUiTests.PairedGatewayNameOnlySavePreservesCredentials" 1 0 0

Connection skips are unrelated Local AI symlink/distinct-volume cases. Skips are not passes. The original head's CI Gate is successful.

The final two rows used an approved validation-only four-test-file patch, retained as an artifact rather than pushed to the contributor branch. Profile/run isolation guards passed before the UI run. The focused UI runner was built without rebuilding product dependencies, then invoked as:

dotnet test .\tests\OpenClaw.Tray.UITests\OpenClaw.Tray.UITests.csproj -c Debug -r win-arm64 --no-build --no-restore --filter FullyQualifiedName~GatewayFixtureUiTests.PairedGatewayNameOnlySavePreservesCredentials --logger "trx;LogFileName=pr1485-ui-save.trx" --results-directory <private-results>

The existing fixture UI opt-in selected the explicitly hash-checked current-head app, private artifacts and owned native paired seed. Screenshots were disabled. The UI worker had a 120-second deadline, its test process had a 180-second outer watchdog, and the owned Gateway had a 600-second bound. The single test passed on its first execution. The validation-only patch was subsequently reversed before prospective integration validation; no tooling changes were pushed to the author branch.

Prospective current-main integration: passed once

The reviewed PR head was locally merged with main cf19144ce18eef173b2cbbad5e4aade11ab4cc80, without an author-branch push or GitHub merge:

  • Local integration commit: 007cca3e04a9cb82ae70ca0c031faa3b82c95804.
  • Parents: 6000006cccde99ccc848b047b9688acbb6532303 and cf19144ce18eef173b2cbbad5e4aade11ab4cc80.
  • Prospective result tree: b8fc7adc17ef1677287a9179b8929bcdfbc2b68c.
  • All three original PR files are byte-identical between the original behavior-proof head and this integration commit. Production blobs: ConnectionPage 992ece1b84f7694ee920a308d2d9107c6b2360a1; GatewayDirectConnectService 0f42416578bb17971ba8860aeee0edbada80616b.
Prospective command Passed Failed Skipped
.\build.ps1 Full build succeeded 0 n/a
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --no-restore 4104 0 35
dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore 3072 0 0
dotnet test .\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj --no-restore --filter "FullyQualifiedName~GatewayDirectConnectServiceTests" 15 0 0

This was one full prospective validation pass with no recovery rerun needed. The UI/native behavior proof remains attributed to the original 6000006c binary, not falsely presented as an integration-binary UI rerun. At closeout, live GitHub head was still 6000006c, main was still cf19144c, the PR was OPEN/CLEAN and all non-skipped checks were successful. Final GitHub merge remains maintainer-coordinated.

Real behavior proof

Native product Gateway: 27 assertions passed

A private external harness ran against the verified official OpenClaw 2026.9.5 package with separate disposable profiles/state/home/workspace, random test-only credentials, and dedicated loopback ports. Gateway child environments were cleared and rebuilt from an OS whitelist; no user/provider keys, service installation, WSL, or normal gateway state were used. Approvals matched the owned identity and exact pending request only.

This harness loaded exact-head production GatewayDirectConnectService from Tray.Tests, which links that source, and used the real CredentialResolver, GatewayConnectionManager, GatewayClientFactory, NodeConnector, and production WebSocket clients. No FakeConnectionManager or protocol fixture supplied authentication results. This service-level proof is separate from the actual UI test below.

Scenario Observed result
Fresh add Actual Gateway paired operator and node; both issued device tokens persisted.
Unchanged token plus rename/save Preservation selected; same record ID and updated friendly name persisted; both device tokens and bootstrap value survived the save boundary.
Reconnect after rename Both roles authenticated with stored device tokens; authenticated operator health RPC succeeded; no re-pair request remained.
Changed, incorrect shared token Prior role credentials cleared before the new shared-token attempt; actual Gateway rejected it; rollback restored original credentials and live connection.
Changed endpoint, identical shared-token text A second actual Gateway received a fresh identity and submitted shared token, not old device/bootstrap credentials. Both roles paired; old local record/identity removed; active record survived reload.
Loopback hostname alias localhost and 127.0.0.1 remained the same credential realm.
SSH host/port change Production same-realm predicate returned false. This is decision-level evidence, not live SSH transport proof.

Actual WinUI Save and reconnect: passed

At 2026-09-24 04:11:40Z, one new GatewayFixtureRun instance connected the actual native app to the owned official Gateway 2026.9.5. It did not reuse the earlier aborted manual app. The approved test-only fixture entry point retained the existing app-launch/disposal boundary and enabled a paired seed copied only from this task's disposable native fixture. Its fresh GUID profile used explicit GatewayFixtureIsolation, an OS-whitelisted child environment, separate app/local roots and a fresh MXC_DACL_STATE_DIR. Optional node capabilities and unrelated host effects were disabled.

After actual operator/node Ready and zero pending approvals, the test captured a private credential baseline. It opened the real saved-row Edit, changed only AddDirectName, and invoked the real AddSave button through UIA. It then reopened Edit and invoked the same button with no field edits. There was no MCP/programmatic settings-write substitute, production reflection, synthetic Gateway response or fabricated client banner.

After each Save, the test required the persisted name, form completion, operator/node Ready using identity.DeviceToken and identity.NodeDeviceToken, zero pending approvals, and exact private equality of shared/device tokens, keypair, URL and gateway identity. It also read these actual visible UIA nodes:

Stage Operator UI Node UI Credential equality Pending approvals
Name-only Save Connected, ConnectionPageMarker, visible 986 x 40 Node active · no capabilities enabled, NodeStatusText, visible 1151 x 28 Unchanged 0
Unchanged Save Connected, ConnectionPageMarker, visible 986 x 40 Node active · no capabilities enabled, NodeStatusText, visible 1151 x 28 Unchanged 0

The node text accurately reflects intentionally disabled optional capabilities. No node capability invocation or MXC containment is claimed. The test captured allowlisted status text, IDs, visibility and dimensions only, not raw accessibility trees, token fields, screenshots or private values. The fixture disposed its own app/profile; the owned backend was stopped and its listener closed.

Original behavior-proof binary mapping: executable SHA-256 89C8453584CC8CCDE48E23009E31363D383DC3AE2C1706A8E65832519726E540; app DLL SHA-256 4B1A8B052C3D464FFE6A5AD3B6590ECCC8FA50C042B853C8BB8709C4DD2163C9. Both match the reviewed-head full build and were unchanged by test-runner compilation. The later prospective integration build is recorded separately above.

Retained safe evidence: counted original and prospective TRX files; pr1485-fixture-guards.trx; pr1485-ui-save.trx; allowlisted native-ui-save-proof.json SHA-256 0060AD5E9228592242B338C85F91C0A11EAD913BF6E7CE384753D3D2A5D826BB; native-run.json; validation-only tooling patch SHA-256 B81DDD176BF593223A4558E6D5DE7591CBF5EE547BA98E741D320FF775623D98; original 27-assertion receipt SHA-256 8BF0C15885FCDCC6368349266021FF8B16C1F8E099ACBC0C5D8FDFCE695CC17D. Raw settings, identities, keys, tokens, machine names and profile paths are not published.

The earlier user-launched attempt established Ready and staged a name edit but never saved; it was safely aborted. It is not counted as Save proof. The subsequent independent automated UI test above closes that gap.

Not verified: live SSH transport negative control and WSL provisioning. The corresponding decision-level SSH coverage and explicit WSL scope calibration are stated above. No replacement PR, force-push, author-branch push or production-source edit was performed by this validation lane.

…ge the shared token

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper

clawsweeper Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Sep 23, 2026
@clawsweeper

clawsweeper Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codex review: needs maintainer review before merge. Reviewed September 24, 2026, 12:19 AM ET / 04:19 UTC (Revision 7).

ClawSweeper review

What this changes

Saving an existing gateway with an unchanged shared token now retains its paired device credentials and bootstrap token, while an endpoint or token change follows the replacement path.

Regression provenance

Possible regression — probable (reviewed change; failure trace). No predecessor PR is attributed.

Merge readiness

✅ Ready for maintainer review

This remains a useful fix: current main still clears paired credentials when the Connection form resubmits an unchanged shared token. The earlier endpoint-change concern is addressed, and the updated exact-head native UI and Gateway evidence supports the changed behavior.

Priority: P2
Reviewed head: 6000006cccde99ccc848b047b9688acbb6532303

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The focused patch has strong exact-head native UI and Gateway evidence, with no discrete blocking defect found.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The exact-head native WinUI test exercised the real saved-row Edit and Save buttons twice, then observed connected operator and node states and unchanged credentials. Separate production-client runs against a real Gateway observed device-token reconnect, changed-token rejection, and fresh pairing at a changed endpoint. Existing saved-state behavior was checked; live SSH transport and WSL provisioning were outside the demonstrated scenario.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The exact-head native WinUI test exercised the real saved-row Edit and Save buttons twice, then observed connected operator and node states and unchanged credentials. Separate production-client runs against a real Gateway observed device-token reconnect, changed-token rejection, and fresh pairing at a changed endpoint. Existing saved-state behavior was checked; live SSH transport and WSL provisioning were outside the demonstrated scenario.
Evidence reviewed 8 items Current-main behavior: The Connection form still submits its prefilled token on Save; the service clears device tokens for a nonempty submitted token and sets the bootstrap token to null.
Introduced credential decision: The page sends a null token with preservation enabled only when the submitted token and credential realm are unchanged.
Service boundary: The service rechecks whether the request stays in the same credential realm, preserves the stored bootstrap token for the preservation path, and clears device tokens only for a submitted nonempty token.
Findings None None.
Security None None.

How this fits together

The Connection page turns saved gateway form values into a direct-connect request. The direct-connect service updates the gateway registry and identity state, then asks the connection manager to authenticate the operator and node.

flowchart LR
  A[Saved gateway form] --> B[Compare token and endpoint]
  B --> C[Direct-connect service]
  C --> D[Gateway registry and identity]
  D --> E[Connection manager]
  E --> F[Operator and node connection]
Loading

Before merge

None.

Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Code and test delta production +60/-8 lines; tests +104/-0 lines The production growth is confined to the credential-preservation decision and is accompanied by focused regression coverage.

Technical review

Best possible solution:

Keep the same-realm preservation check in the form and transaction service, with endpoint changes continuing to establish a fresh identity.

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

No current-main run was performed in this read-only review. The current-main form and service provide a high-confidence source path for the reported credential clearing.

Is this the best way to solve the issue?

Yes. The patch changes the form decision while retaining the service as the credential transaction owner, and it guards endpoint changes before preserving credentials.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against cf19144ce18e.

Labels

Label changes:

No label changes.

Label justifications:

  • P2: The patch fixes a bounded saved-gateway authentication regression.
  • merge-risk: 🚨 auth-provider: The Save path decides whether stored authentication tokens survive; exact-head native Gateway proof covers unchanged and changed credential realms.
  • 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 (live_output): The exact-head native WinUI test exercised the real saved-row Edit and Save buttons twice, then observed connected operator and node states and unchanged credentials. Separate production-client runs against a real Gateway observed device-token reconnect, changed-token rejection, and fresh pairing at a changed endpoint. Existing saved-state behavior was checked; live SSH transport and WSL provisioning were outside the demonstrated scenario.
  • proof: sufficient: Contributor real behavior proof is sufficient. The exact-head native WinUI test exercised the real saved-row Edit and Save buttons twice, then observed connected operator and node states and unchanged credentials. Separate production-client runs against a real Gateway observed device-token reconnect, changed-token rejection, and fresh pairing at a changed endpoint. Existing saved-state behavior was checked; live SSH transport and WSL provisioning were outside the demonstrated scenario.

Evidence

What I checked:

  • Current-main behavior: The Connection form still submits its prefilled token on Save; the service clears device tokens for a nonempty submitted token and sets the bootstrap token to null. (src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs:2847, cf19144ce18e)
  • Introduced credential decision: The page sends a null token with preservation enabled only when the submitted token and credential realm are unchanged. (src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs:2850, 6000006cccde)
  • Service boundary: The service rechecks whether the request stays in the same credential realm, preserves the stored bootstrap token for the preservation path, and clears device tokens only for a submitted nonempty token. (src/OpenClaw.Tray.WinUI/Services/GatewayDirectConnectService.cs:265, 6000006cccde)
  • Current-head proof: The PR body reports an isolated native WinUI test that invoked the real Save button twice and checked visible connected states and private credential equality after each Save. It separately reports real Gateway authentication, reconnect, changed-token rejection, and changed-endpoint pairing; live SSH transport and WSL provisioning were expressly not claimed. (6000006cccde)
  • Prior review continuity: The earlier endpoint-change finding is addressed by the second PR commit. The prior request for a completed WinUI Save observation is answered by the updated native UI evidence; the reported WSL provisioning gap does not exercise this existing-record form decision. (tests/OpenClaw.Tray.Tests/GatewayDirectConnectServiceTests.cs:306, 6000006cccde)
  • Architecture policy: The architecture ledger assigns the direct-connect transaction to the service and leaves form input and rendering with the Connection page. The patch keeps that ownership boundary. (docs/ARCHITECTURE.md:180, 6000006cccde)

Likely related people:

  • unknown: The claimed source-line change could not be verified from bounded local history. (role: source history unknown; confidence: low)
  • Scott Hanselman: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)
  • karkarl: Suggested for follow-up; no historical authorship or introduction is verified. (role: unverified routing candidate; confidence: low)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
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.

Workflow

  • 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.

History

Review history (6 earlier review cycles)
  • reviewed 2026-09-23T02:23:27.581Z sha 2a96604 :: needs real behavior proof before merge. :: [P1] Preserve the submitted token when the gateway endpoint changes
  • reviewed 2026-09-23T04:44:07.863Z sha 6000006 :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-23T18:06:15.432Z sha 6000006 :: needs real behavior proof before merge. :: none
  • reviewed 2026-09-23T18:24:48.563Z sha 6000006 :: blocked before merge. :: none
  • reviewed 2026-09-23T20:14:15.861Z sha 6000006 :: blocked before merge. :: none
  • reviewed 2026-09-24T01:34:18.816Z sha 6000006 :: blocked before merge. :: none

…hanges

Preserve an unchanged shared token only inside the same credential realm. An endpoint edit must send the token the user submitted.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@shanselman shanselman added the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Sep 23, 2026
@karkarl

karkarl commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Global triage: TAKE_AFTER_CHECKS. Take confidence 82%; recommendation confidence 92%; effort small; risk medium.

Reviewed exact head 6000006cccde. Resubmitting an unchanged shared token caused ConnectAsync to clear paired device credentials. The page and service independently guard preservation, realm comparison covers SSH endpoint changes, and blanking still clears credentials. Blocker is proof only: provide windows-wsl-gateway-e2e evidence showing a paired gateway renamed, saved, and reconnecting with its retained device token. Minor: stored token is trimmed while submitted token is not, so whitespace padding falls back to the destructive path, though it fails safe.

@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Sep 23, 2026
@shanselman shanselman removed the status: 🚢 actively landing A maintainer or agent is actively driving this item through implementation, validation, or merge. label Sep 23, 2026
@clawsweeper clawsweeper Bot added rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Sep 23, 2026
@shanselman
shanselman merged commit 0e45bb6 into openclaw:main Sep 24, 2026
33 checks passed
@SebTardif
SebTardif deleted the fix/f021-unchanged-shared-token branch September 24, 2026 05:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 Merging this PR could break OAuth, tokens, provider routing, model choice, or credentials. P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants