You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(cloud): an expired session is a sign-in card, found before the first message — not a 401 in red
A user back from a week away typed a goal and got "LevelCode Cloud API 401: Signature has
expired" in the transcript, while the account popover said they were signed in. Three
things made that possible, and each is closed here.
The session could die on a schedule nobody could see. The editor stored the access token
the refresh endpoint returned and kept its ORIGINAL refresh token forever, so the 30 days
ran from the last sign-in. The server now rotates the refresh token (thin.ly #438) and the
editor stores it, so the window slides with use.
"Signed in" meant "a secret exists". cloudSignedIn was the presence of a token in
SecretStorage, never its validity, so dead credentials kept the footer on "Gateway · Max"
and the popover on "Manage account". refreshCloudToken now classifies the refresh reply
(providers/session.js): only an explicit 401 ends the session — offline and 5xx keep the
tokens, because clearing credentials on a network blip would log someone out for closing
their laptop on the train. When it IS over, sessionExpired() forgets the tokens, flips the
flag, resyncs the popover, and tells the webview.
The failure was discovered by the user's first message. The access token's own `exp` is
now read locally at the webview's ready and on window focus (throttled), and the network
is touched only when it is expired or within five minutes of it — so an expiry found on
launch is a sign-in card at the top of an empty chat, not the reply to a goal they just
typed. A send that still finds the session dead — chat, agent, or a prep failure in
gateway mode with no token — posts code 'session_expired', which the webview routes to the
card ahead of the cap and service cards and the red-text fallback. Gateway mode with no
token is named for what it is, "signed out", rather than "No API key set for OpenAI".
Verified: session 12 tests, sessionExpiredUi 10; full suite 42 suites, 621 cases, 0
failing. Each guard reverted in turn fails only its own assertion: the session check moved
behind the cap card -> the ordering guard; agent.js no longer naming the dead session ->
the agent assertion; the ready check removed -> the startup guard. No tsconfig exists, so
node --check is the syntax gate.
Copy file name to clipboardExpand all lines: extensions/levelcode-ai/agent.js
+6Lines changed: 6 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -1048,6 +1048,12 @@ async function runAgent(ctx) {
1048
1048
ctx.post({type: 'agentError',message: 'You’ve hit the model’s context window (the conversation got too long). Start a New chat to reset it, switch to a larger-context model, or pin fewer files — then continue.',kind: 'context'});
@@ -202,6 +203,7 @@ function providerErrorMessage(req) {
202
203
if(req.reason==='baseURL'){return'Set a base URL for the custom OpenAI-compatible provider first (levelcode.ai.baseURL).';}
203
204
if(req.reason==='insecureBaseURL'){return'Refusing to send your API key over plain http to a non-local host. Use an https base URL (or a localhost endpoint) for the custom provider.';}
204
205
if(req.reason==='insecureGateway'){return'Refusing to send your LevelCode Cloud token over plain http. Set "levelcode.cloud.endpoint" to an https URL to use gateway mode.';}
0 commit comments