Fix frontend settings auth and backend URL wiring - #31
Conversation
Co-authored-by: up2itnow0822 <up2itnow0822@gmail.com>
Co-authored-by: up2itnow0822 <up2itnow0822@gmail.com>
| const handleSave = () => { | ||
| persistBackendUrl(backendUrl); | ||
| persistBackendBaseUrl(backendUrl); | ||
| const token = apiKeyInputRef.current?.value; | ||
| if (token) { | ||
| setAuthTokenForSession(token); | ||
| apiKeyInputRef.current.value = ''; | ||
| } | ||
|
|
||
| setSaved(true); | ||
| setTimeout(() => { | ||
| setSaved(false); | ||
| onClose(); | ||
| window.location.reload(); // Reload to apply changes to API client | ||
| }, 1000); | ||
| }; |
There was a problem hiding this comment.
In-memory auth token cannot be cleared via the UI
handleSave only calls setAuthTokenForSession when the input is non-empty (if (token) { ... }). The input is blanked on every modal open (line 19), so a user who previously set a token will see an empty field and naturally expect "Save Changes" with no token entered to leave them with no active auth. Instead, the prior token silently persists until a full page reload. An identical issue exists in settings/page.tsx. The help text says "until the next full page reload" but there is no explicit opt-out path — saving with an empty token field gives the same result as not saving at all.
| expect(receivedRequest.url).toBe('/api/agents'); | ||
| expect(receivedRequest.headers.authorization).toBe('Bearer protected-token'); |
There was a problem hiding this comment.
receivedRequest may be undefined if api.get('/agents') throws before the server handler fires. Accessing .url on undefined produces a TypeError rather than a failing Jest assertion, making the failure hard to diagnose. Adding an explicit toBeDefined() guard makes the failure message actionable.
| expect(receivedRequest.url).toBe('/api/agents'); | |
| expect(receivedRequest.headers.authorization).toBe('Bearer protected-token'); | |
| expect(receivedRequest).toBeDefined(); | |
| expect(receivedRequest.url).toBe('/api/agents'); | |
| expect(receivedRequest.headers.authorization).toBe('Bearer protected-token'); |
| { | ||
| "extends": "next/core-web-vitals", | ||
| "root": true, | ||
| "parser": "@typescript-eslint/parser", | ||
| "plugins": [ | ||
| "@typescript-eslint", | ||
| "react-hooks" | ||
| ], | ||
| "env": { | ||
| "browser": true, | ||
| "es2022": true | ||
| }, | ||
| "parserOptions": { | ||
| "ecmaVersion": 2022, | ||
| "sourceType": "module", | ||
| "ecmaFeatures": { | ||
| "jsx": true | ||
| } | ||
| }, | ||
| "rules": { | ||
| "@typescript-eslint/explicit-function-return-type": "off", | ||
| "@typescript-eslint/no-explicit-any": "off" | ||
| "no-undef": "off", | ||
| "no-unused-vars": "off", | ||
| "@typescript-eslint/no-unused-vars": [ | ||
| "warn", | ||
| { | ||
| "argsIgnorePattern": "^_", | ||
| "varsIgnorePattern": "^_" | ||
| } | ||
| ], | ||
| "react-hooks/rules-of-hooks": "error", | ||
| "react-hooks/exhaustive-deps": "warn" | ||
| } |
There was a problem hiding this comment.
next/core-web-vitals rules are no longer enforced
Replacing extends: "next/core-web-vitals" with a hand-rolled config silently drops all @next/next rules (e.g., no-html-link-for-pages, no-img-element, no-page-custom-font). The package.json lint script also switches from next lint to plain eslint src, so the Next.js plugin is not loaded at all. If the goal is to unblock ESLint under Next 16, consider re-adding @next/eslint-plugin-next explicitly rather than dropping the entire ruleset.
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!


Bug and impact
Recent settings hardening split the backend URL across multiple localStorage keys and stopped forwarding the auth token entered in Settings. The API client still read only
api_urlandauth_token, so protected API flows silently hit the wrong backend and/or returned 401 after users saved settings.Root cause
The settings modal and settings page persisted
backend_base_url/agentnexus_backend_url, whilefrontend/src/lib/api.tsonly consumedapi_url. Auth inputs were converted to uncontrolled refs but the save handlers never read them, and the API interceptor still looked for a persisted token that was no longer written.Fix
dev --webpack, direct ESLint, Jest ignoring E2E/build artifacts) and updated viem to a compatible latest 2.x version so production builds succeed.Validation
pnpm --filter @agentnexus/frontend test -- --runInBandpnpm --filter @agentnexus/frontend type-checkpnpm --filter @agentnexus/frontend lint(passes with 3 pre-existing warnings)pnpm --filter @agentnexus/frontend buildSETTINGS_SCREENSHOT_PATH=/opt/cursor/artifacts/agentnexus_settings_saved.png pnpm --filter @agentnexus/frontend exec playwright test e2e/settings.spec.ts --project=chromiumpython3 -m bandit -q -r backend/src/agents agents agent-runtime/docker/python-agent --skip B101python3 -m ruff check backend/src/agents agents agent-runtime/docker/python-agentpython3 -m pytest agents/paper-trader/test_price_feed.pymdformat --checknot applicable to this patch: no Markdown files changed. Full-repo mdformat has a pre-existing formatting baseline across many docs.Greptile Summary
This PR fixes the settings-to-API wiring by introducing a shared
client-settings.tshelper that unifies four legacy localStorage key aliases for the backend URL and provides a reload-scoped in-memory auth token, then threads both through the axios interceptors and both Settings UIs.client-settings.ts+api.ts: A new shared helper normalises the four competing URL keys, persists the URL to all of them on save, and exposes a module-level in-memory auth token forwarded asAuthorization: Beareron every request.window.location.reload()are removed in favour of the interceptor re-reading the URL on each request.price_feed.pygains a URL scheme guard beforeurlopen,main.pyupgrades from MD5 to SHA-256 for the deterministic price seed, and ESLint is reconfigured to unblock Next 16 builds.Confidence Score: 3/5
The auth-token wiring fix is correct but introduces a silent no-op when a user saves settings with an empty token field — the previously set token is not cleared, meaning unintended authenticated requests can continue without the user realising it.
The core URL-alias unification and auth-header forwarding work correctly. Once an in-memory token has been set, there is no UI path to revoke it short of a full page reload. A user who opens the Settings modal, sees an empty token field, and clicks Save will silently continue sending the old token. Given the removal of the post-save page reload that previously served as an implicit token reset, this gap is more reachable than before.
frontend/src/components/layout/SettingsModal.tsxandfrontend/src/app/settings/page.tsxboth contain the token-clearing gap and should be reviewed together before merge.Important Files Changed
Sequence Diagram
sequenceDiagram participant User participant SettingsUI as Settings UI participant clientSettings as client-settings.ts participant localStorage participant axiosInterceptor as api.ts interceptor participant Backend User->>SettingsUI: Enter backend URL + optional token, Save SettingsUI->>clientSettings: persistBackendBaseUrl(url) clientSettings->>localStorage: setItem(all 4 URL alias keys, url) SettingsUI->>clientSettings: setAuthTokenForSession(token) only if non-empty clientSettings-->>clientSettings: "authToken = token" User->>Backend: Trigger API call axiosInterceptor->>clientSettings: getBackendBaseUrl() clientSettings->>localStorage: getItem first matching key localStorage-->>axiosInterceptor: saved URL axiosInterceptor->>clientSettings: getAuthTokenForSession() clientSettings-->>axiosInterceptor: in-memory authToken axiosInterceptor->>Backend: request with Authorization header Backend-->>axiosInterceptor: responseReviews (1): Last reviewed commit: "chore: harden python sample quality gate..." | Re-trigger Greptile