Skip to content

Fix settings API client storage contract#35

Draft
cursor[bot] wants to merge 3 commits into
mainfrom
cloud/critical-bug-investigation-d957
Draft

Fix settings API client storage contract#35
cursor[bot] wants to merge 3 commits into
mainfrom
cloud/critical-bug-investigation-d957

Conversation

@cursor

@cursor cursor Bot commented Jun 9, 2026

Copy link
Copy Markdown

Bug and impact

Settings saved backend URLs under keys the API client did not read, and auth tokens entered in Settings were not written to the session storage path used by requests. Custom backend deployments continued using the default API URL, and authenticated flows such as executions/builder calls could 401 after users saved a token.

Root cause

Recent settings hardening renamed/disconnected storage keys while frontend/src/lib/api.ts still read only api_url and auth_token from localStorage.

Fix

  • Added a shared API settings helper for backend URL and session-scoped auth token storage.
  • Wired both Settings surfaces and the API client to the same helper.
  • Preserved legacy URL key fallback and legacy token fallback while writing tokens only to sessionStorage.
  • Upgraded frontend viem to satisfy the existing wagmi import surface and restore production build.

Validation

  • pnpm --filter @agentnexus/frontend test -- apiConfig.test.ts --runInBand
  • pnpm --filter @agentnexus/frontend type-check
  • pnpm --filter @agentnexus/frontend build
  • Manual browser walkthrough saved API endpoint + session token successfully.
Open in Web View Automation 

Greptile Summary

This PR centralises storage for the API base URL and auth token into a shared apiConfig.ts helper, then wires both Settings surfaces and the axios client to it. It also bumps viem to ^2.52.2 to unblock the production build.

  • URL storage is fixed correctly: persistApiBaseUrl writes all three legacy localStorage keys atomically, so getApiBaseUrl always finds the saved value regardless of which key the old code used.
  • Token storage introduces an asymmetry: persistSessionAuthToken writes only to sessionStorage, but getAuthToken falls back to localStorage. Because sessionStorage is scoped to a single tab and cleared on new sessions, a legacy auth_token value in localStorage will silently resurface whenever the user opens a new tab or restarts the browser — even after the user has saved a different token through Settings.
  • No-op on blank: persistSessionAuthToken('') does nothing, so there is also no UI path to revoke an active or legacy token.

Confidence Score: 3/5

The URL-routing fix is safe to ship; the token-handling path has a gap where the old localStorage token can silently take over in new tabs or sessions.

The core URL fix is solid and properly unifies the three legacy keys. The token handling writes the new value only to sessionStorage while leaving any pre-existing localStorage token untouched and readable as a fallback. A user who saves a new token via Settings will silently revert to their old token the moment they open a new tab or restart the browser — the exact authentication regression the PR aims to prevent. There is also no way to fully revoke a token through the Settings UI.

frontend/src/lib/apiConfig.ts — the persistSessionAuthToken / getAuthToken pair needs a localStorage eviction step and a clear-on-empty branch before the token fix is complete.

Important Files Changed

Filename Overview
frontend/src/lib/apiConfig.ts New shared helper module — URL persistence is correct (writes all keys atomically), but token persistence is asymmetric: writes only to sessionStorage while getAuthToken falls back to localStorage, causing old localStorage tokens to resurface in new tabs/sessions after a Settings save.
frontend/src/lib/apiConfig.test.ts New test file covering the four main behaviours; the 'does not clear on blank' test explicitly encodes the design that the P1 comment flags — no test covers the cross-tab scenario where sessionStorage is empty and a legacy localStorage token re-emerges.
frontend/src/lib/api.ts Cleanly delegates URL and token resolution to the new helpers; both interceptors are straightforward and correct.
frontend/src/app/settings/page.tsx Wired to the new helpers correctly; blank-token-on-save passes an empty string to persistSessionAuthToken, which is the intended no-op behaviour.
frontend/src/components/layout/SettingsModal.tsx Old inline storage helpers removed in favour of shared apiConfig; default placeholder updated to 3001 to match DEFAULT_API_BASE_URL.
frontend/package.json viem bumped from ^2.21.58 to ^2.52.2, overrides block removed, @types/jest added — routine dependency hygiene.
pnpm-lock.yaml Lock file updated consistently with package.json changes; all viem-dependent packages resolved to 2.52.2.

Sequence Diagram

sequenceDiagram
    participant User
    participant SettingsUI as Settings UI (page / modal)
    participant apiConfig as apiConfig.ts
    participant LS as localStorage
    participant SS as sessionStorage
    participant ApiClient as api.ts (axios)

    Note over User,ApiClient: Save backend URL
    User->>SettingsUI: Enter URL + click Save
    SettingsUI->>apiConfig: persistApiBaseUrl(url)
    apiConfig->>LS: setItem(api_url, url)
    apiConfig->>LS: setItem(backend_base_url, url)
    apiConfig->>LS: setItem(agentnexus_backend_url, url)

    Note over User,ApiClient: Save auth token
    User->>SettingsUI: Enter token + click Save
    SettingsUI->>apiConfig: persistSessionAuthToken(token)
    apiConfig->>SS: setItem(auth_token, token)
    Note right of LS: legacy auth_token in LS never cleared

    Note over User,ApiClient: Outgoing API request
    ApiClient->>apiConfig: getApiBaseUrl()
    apiConfig->>LS: getItem(api_url) → url
    ApiClient->>apiConfig: getAuthToken()
    apiConfig->>SS: getItem(auth_token)
    alt sessionStorage has token
        SS-->>apiConfig: session-token
    else sessionStorage empty (new tab / new session)
        apiConfig->>LS: getItem(auth_token)
        LS-->>apiConfig: legacy-token
    end
    apiConfig-->>ApiClient: token
    ApiClient->>ApiClient: set Authorization header
Loading

Fix All in Codex Fix All in Cursor

Reviews (1): Last reviewed commit: "fix: reconnect settings to API client" | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Co-authored-by: up2itnow0822 <up2itnow0822@gmail.com>
Comment thread frontend/src/lib/apiConfig.ts Fixed
Comment on lines +29 to +44
export function getAuthToken(): string | null {
if (typeof window === 'undefined') return null;

return (
window.sessionStorage.getItem(AUTH_TOKEN_KEY) ||
window.localStorage.getItem(AUTH_TOKEN_KEY)
);
}

export function persistSessionAuthToken(token: string): void {
if (typeof window === 'undefined') return;

const value = token.trim();
if (value) {
window.sessionStorage.setItem(AUTH_TOKEN_KEY, value);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Legacy localStorage token re-emerges in new tabs / new sessions

persistSessionAuthToken writes only to sessionStorage, but getAuthToken falls back to localStorage when sessionStorage is empty. Because sessionStorage is per-tab and cleared on new sessions, a user who saves a new token via Settings will find that token applied only in the current tab — any new tab, or any browser restart, causes getAuthToken to silently return the old localStorage token instead. The URL save path avoids this by writing all three localStorage keys at once; the token path has no equivalent cleanup.

At minimum, writing a non-empty session token should also evict the legacy localStorage entry so it cannot resurface.

Fix in Codex Fix in Cursor

Comment on lines +38 to +45
export function persistSessionAuthToken(token: string): void {
if (typeof window === 'undefined') return;

const value = token.trim();
if (value) {
window.sessionStorage.setItem(AUTH_TOKEN_KEY, value);
}
}

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 No way to revoke a session token via the Settings UI — persistSessionAuthToken('') is a complete no-op. Once a user saves a token they cannot clear it through Settings; they must manually open DevTools to remove it. Adding an else branch to remove both the sessionStorage and legacy localStorage entries when the value is empty would give users a way to revoke the token.

Suggested change
export function persistSessionAuthToken(token: string): void {
if (typeof window === 'undefined') return;
const value = token.trim();
if (value) {
window.sessionStorage.setItem(AUTH_TOKEN_KEY, value);
}
}
export function persistSessionAuthToken(token: string): void {
if (typeof window === 'undefined') return;
const value = token.trim();
if (value) {
window.sessionStorage.setItem(AUTH_TOKEN_KEY, value);
// Evict any legacy localStorage token so it cannot resurface in new tabs/sessions.
window.localStorage.removeItem(AUTH_TOKEN_KEY);
} else {
window.sessionStorage.removeItem(AUTH_TOKEN_KEY);
window.localStorage.removeItem(AUTH_TOKEN_KEY);
}
}

Fix in Codex Fix in Cursor

Comment thread frontend/src/lib/apiConfig.ts Outdated
Comment on lines +32 to +35
return (
window.sessionStorage.getItem(AUTH_TOKEN_KEY) ||
window.localStorage.getItem(AUTH_TOKEN_KEY)
);

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 || treats an empty-string sessionStorage value as missing

sessionStorage.getItem returns null when the key is absent and '' when the key exists with an empty string. Using || means an empty-string value silently falls through to the localStorage fallback. Using ?? (nullish coalescing) restricts the fallback to the null case only, which is the actual "key not set" condition.

Fix in Codex Fix in Cursor

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agent-OS PR Cleanup — Draft Assessment

PR #35 is the most complete of six overlapping draft PRs (#30#35) that all target frontend settings/API client persistence.

What this PR does

  • Introduces apiConfig.ts as a single source of truth for backend URL and auth token
  • Migrates legacy localStorage keys (api_url, backend_base_url, agentnexus_backend_url)
  • Keeps auth tokens session-scoped in memory (with legacy localStorage fallback)
  • Adds unit tests (apiConfig.test.ts) — all 4 pass locally

Consolidation recommendation

Close duplicate drafts #30#34 after merging this PR. They solve the same problem with partial/incremental approaches.

Local verification

cd frontend && pnpm install --frozen-lockfile && pnpm exec jest src/lib/apiConfig.test.ts
# 4 passed

Ready for review once marked non-draft.

Open in Web View Automation 

Sent by Cursor Automation: Untitled

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants