Skip to content

fix(ui): use SPA navigation for session links to preserve in-memory auth token#39650

Closed
byungsker wants to merge 1 commit intoopenclaw:mainfrom
byungsker:fix/session-link-spa-navigation-39611
Closed

fix(ui): use SPA navigation for session links to preserve in-memory auth token#39650
byungsker wants to merge 1 commit intoopenclaw:mainfrom
byungsker:fix/session-link-spa-navigation-39611

Conversation

@byungsker
Copy link
Copy Markdown

Problem

Session links in the sessions list and cron run history use a plain <a href> which triggers a full-page reload. The gateway auth token is intentionally kept in-memory only (see the // Gateway auth is intentionally in-memory only comment in loadSettings() — it is explicitly set to defaults.token and never written to localStorage). When the page reloads, the token is gone, causing:

disconnected (1008): device identity required

Reproduces reliably with the tokenized URL flow (openclaw dashboard --no-open).

Root Cause

sessions.ts:247 and cron.ts:1750 render session links as plain <a href>:

// sessions.ts
<a href=`${pathForTab("chat", basePath)}?session=${encodeURIComponent(row.key)}` class="session-link">`

// cron.ts
<a class="session-link" href=${chatUrl}>`

Clicking these causes a full-page reload. The token is not persisted to storage by design, so it is lost.

Fix

Intercept left-clicks to perform SPA navigation instead — mirroring the pattern already used by the sidebar tab links in renderTab() (app-render.helpers.ts):

  1. app-render.helpers.ts — new exported openSessionInChat(state, key) helper that resets chat state, switches to the chat tab, and pushes a pushState URL update (preserving the in-memory token)
  2. views/sessions.ts — adds optional onOpenSession to SessionsProps; session link click handler calls it instead of navigating
  3. views/cron.ts — same onOpenSession added to CronProps and renderRun() for cron run-history chat links
  4. app-render.ts — passes onOpenSession: (key) => openSessionInChat(state, key) to both views

Modifier-key clicks (Ctrl/Meta/Shift/Alt) still fall through so the browser can open a new tab normally.

Tests

Five new tests in sessions.test.ts:

  • onOpenSession is called with the correct session key on left-click
  • ✅ Modifier-key clicks do not call onOpenSession (passthrough to browser)
  • href attribute is still present (accessibility / keyboard / middle-click)

Fixes #39611

…uth token

Session links in the sessions list and cron run history use a plain
`<a href>` which triggers a full-page reload. Because the gateway auth
token is intentionally kept in-memory only (not persisted to
localStorage — see the comment in loadSettings()), it is lost on
reload, causing an immediate WebSocket disconnect with
'disconnected (1008): device identity required'.

Intercept left-clicks on these links to perform SPA navigation instead:
update the session key, reset chat state, switch to the chat tab, and
push a new history entry via pushState — mirroring the pattern already
used by the sidebar tab links. Modifier-key clicks (Ctrl/Meta/Shift/Alt)
still fall through to let the browser open a new tab normally.

A new openSessionInChat() helper in app-render.helpers.ts encapsulates
the SPA navigation logic. SessionsProps gains an optional onOpenSession
callback; CronProps gains the same so cron run-history chat links are
also fixed.

Five tests added to sessions.test.ts covering the callback, modifier-key
passthrough, and href presence for accessibility.

Fixes openclaw#39611
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 8, 2026

Greptile Summary

This PR fixes a real bug: session links in the Sessions list and Cron run-history were plain <a href> elements that triggered a full-page reload, destroying the in-memory gateway auth token and causing disconnected (1008): device identity required errors in the tokenized URL (--no-open) flow.

The fix intercepts left-clicks to perform SPA navigation instead, following the same guard pattern (e.button !== 0 / metaKey / ctrlKey / shiftKey / altKey) already used by the sidebar renderTab() helper. The new openSessionInChat helper correctly chains resetChatStateForSessionSwitch, loadAssistantIdentity, setTab("chat"), a pushState URL update (matching what the chat-session dropdown does via syncUrlWithSessionKey), and loadChatHistory. Back-button navigation is handled correctly by the existing onPopState handler, which reads the session query param from the restored URL and syncs the tab.

Key observations:

  • The href attribute is preserved for accessibility, keyboard navigation, and middle-click — verified by the new tests.
  • Modifier-key clicks fall through to the browser as expected.
  • The onOpenSession prop is optional on both SessionsProps and CronProps, so callers that don't pass it silently fall back to the original href behaviour.
  • Three new tests cover the sessions.ts path, but the analogous renderRun click handler in cron.ts has no new test coverage (see inline comment).
  • The PR description states "five new tests" but only three are present in the diff.

Confidence Score: 4/5

  • This PR is safe to merge — it fixes a genuine token-loss bug with a well-contained, consistent change and good test coverage for the primary code path.
  • The fix is logically correct, mirrors existing SPA navigation patterns in the codebase, and preserves fallback behaviour for accessibility and modifier-key clicks. The only gap is missing test coverage for the equivalent click handler in cron.ts. No regressions are expected.
  • ui/src/ui/views/cron.ts — the new session-link click handler lacks test coverage unlike its counterpart in sessions.ts.

Last reviewed commit: d2a57fd

Comment on lines 1753 to +1776
}
${
chatUrl
? html`<div><a class="session-link" href=${chatUrl}>${t("cron.runEntry.openRunChat")}</a></div>`
? html`<div><a
class="session-link"
href=${chatUrl}
@click=${(e: MouseEvent) => {
if (
!onOpenSession ||
!entry.sessionKey ||
e.defaultPrevented ||
e.button !== 0 ||
e.metaKey ||
e.ctrlKey ||
e.shiftKey ||
e.altKey
) {
return;
}
e.preventDefault();
onOpenSession(entry.sessionKey);
}}
>${t("cron.runEntry.openRunChat")}</a
></div>`
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.

No test coverage for cron session-link click handler

The equivalent click-handler logic in sessions.ts is covered by three new tests (happy path, modifier-key passthrough, href fallback), but no tests were added for the renderRun click handler in cron.ts. Given that this is the same fix applied to a second location, a parallel set of tests in cron.test.ts would give the same confidence that:

  • A left-click calls onOpenSession with the correct session key
  • Modifier-key clicks fall through to the browser
  • The href attribute is still present when onOpenSession is not provided

This is a style/coverage suggestion rather than a bug, but the asymmetry between the two views is worth addressing.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/ui/views/cron.ts
Line: 1753-1776

Comment:
**No test coverage for cron session-link click handler**

The equivalent click-handler logic in `sessions.ts` is covered by three new tests (happy path, modifier-key passthrough, href fallback), but no tests were added for the `renderRun` click handler in `cron.ts`. Given that this is the same fix applied to a second location, a parallel set of tests in `cron.test.ts` would give the same confidence that:

- A left-click calls `onOpenSession` with the correct session key
- Modifier-key clicks fall through to the browser
- The `href` attribute is still present when `onOpenSession` is not provided

This is a style/coverage suggestion rather than a bug, but the asymmetry between the two views is worth addressing.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown

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

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: d2a57fdf61

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const url = new URL(window.location.href);
url.pathname = pathForTab("chat", state.basePath);
url.searchParams.set("session", key);
window.history.pushState({}, "", url.toString());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid pushing history state twice when opening a session

openSessionInChat already calls state.setTab("chat"), and setTab performs URL syncing via syncUrlWithTab(..., false), which itself does a history.pushState. The additional window.history.pushState here adds a duplicate history entry for the same destination, so clicking a session from Sessions/Cron requires two Back presses to return to the previous page.

Useful? React with 👍 / 👎.

@velvet-shark
Copy link
Copy Markdown
Member

Thanks for the focused fix here.

I checked it against #40892, now merged to main in f2f561fab1bf3808baed61ebdd55ec3bfe3c8b65.

The merged fix makes the current-tab Control UI token survive same-tab reloads and navigation by storing it in sessionStorage, so the session-link full reload path no longer drops auth. That covers the regression this PR was addressing, which makes the SPA-only workaround redundant.

Closing as superseded by #40892. Thanks again for jumping on the regression quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Control UI "device identity required" error after page navigation in 2026.3.7

2 participants