Operator session expires silently — UI keeps 401ing instead of bouncing to /unlock #108
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lz/agent-nexus#108
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
After Nexus has been left open for a while, operations stop working. Nothing tells the operator why — buttons fail, the terminal shows "reconnect failed / the workspace may not be running". Logging out and logging back in restores everything immediately.
Diagnosis
Confirmed in the code; two independent defects compound.
1. The session TTL is absolute, not sliding
SessionStore.create()stampsexpiresAt = Date.now() + ttlonce andget()never extends it (nexus/src/lib/server/auth/session.ts:19,:26). Withsession_ttl_hoursdefaulting to 12 (settings/registry.ts:107), an operator who has the tab open and is actively using it is logged out 12 hours after unlocking, regardless of activity.The store is also in-memory, so a Nexus restart drops every session — same symptom, no TTL involved.
2. A 401 is never surfaced or acted on
Once the session is gone,
hooks.server.ts:29returns401 {"error":"locked"}for every/api/*route. On the client:call()innexus/src/lib/api/client.ts:42throwsApiError(401, 'locked'). Nothing anywhere catches 401 specifically — there is no global handler that redirects to/unlock, nogrephit for it outside the server routes.+layout.server.ts, which only runs on a navigation. An SPA that is just polling never re-runs it, so the app sits there indefinitely in a broken state.terminal/upgrade.ts:97, but a browser exposes a rejected handshake as code 1006 with an empty reason, soSessionTerminal.sveltefalls back to the hedged message "the workspace may not be running" — which points the operator at the wrong cause entirely.So the session dies, every request 401s, and the only feedback is a misleading terminal message.
Suggested fix
Smallest change that addresses the reported symptom:
expiresAtinSessionStore.get()on each hit, and re-issue the cookie (maxAge) from a hook so the browser cookie tracks the server session. An idle timeout stays meaningful; an active operator is never logged out mid-work.call()— on401 locked, redirect to/unlock(goto('/unlock')/ hardlocation.assign), which is the state the app is actually in. This also covers the Nexus-restart case that no TTL change can fix.Optional follow-up: pair with #49 so the post-unlock redirect returns to the page the operator was on.
Not covered here
Whether the terminal overlay should distinguish 401 from 409 — it genuinely cannot from the WS close code alone. Once (2) lands, the app will have navigated to
/unlockbefore the operator reads that overlay, so the misleading string stops being reachable in this scenario.