Skip to content

fix: fix Web Console hanging when SSO token refresh fails - #603

Closed
glasstiger wants to merge 54 commits into
mainfrom
ia_token_refresh_fix
Closed

glasstiger wants to merge 54 commits into
mainfrom
ia_token_refresh_fix

Conversation

@glasstiger

@glasstiger glasstiger commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Review follow-up pushed in 9da50a589c36e7bf1d8719f8db919f0e6636b3fd.

Summary

Fix the Web Console hanging indefinitely when an SSO token refresh fails, and
make query execution and notebook persistence respect the authentication session
that started the work.

Refreshing is shared across SQL consumers, cancellable and bounded. Queries
normally wait up to 10 seconds; a rotating refresh request can continue until
its separate hard deadline (at most 60 seconds). A server-side 401 recovery
waits for that request rather than cancelling a potentially usable rotated
refresh token.

Transient transport, timeout, HTTP 429 and 5xx failures back off without forcing
logout. Once the bearer expires, SQL is not sent until renewal succeeds. The
console reports that explicitly, rather than reporting a database outage or
telling an agent that a never-sent write may have committed. An authoritative
OAuth rejection still returns the user to sign-in.

Complete scope

  • Authentication and token installation: strict token-response parsing;
    optional expiry handling; ID-token bearer expiry; refresh-token rotation;
    missing required ID-token errors; callback URL restoration; tab-local session
    generations; stable provider callbacks and subscription cleanup.
  • Query client: one shared console/telemetry client; send-time authorization
    for SQL, validation, import and preferences requests; bounded refresh waits;
    one safe retry after a rejected bearer; session-aware result/error fencing;
    explicit session-ended and not-sent outcomes.
  • Session teardown: clear client credentials and refresh callbacks, abort
    live queries, cancel delayed telemetry polls, and reset active/pending query
    confirmations (including confirmations already waiting for cancellation).
  • Editor and notifications: transient, query-id-indexed LOADING state is
    excluded from notification history; settled/superseded loading notifications
    are cleaned up; the notification Stop button only cancels its own execution;
    scripts preserve confirmed outcomes and report partial completion on logout.
    Script teardown is shared, without an extra settlement dispatch per statement.
  • Notebook execution and storage: fence live, headless and refresh work;
    separate layout-phase detachment from passive destruction; flush frames
    accepted before logout while discarding late responses; atomically save
    accepted headless metadata/snapshots; recover with metadata-only persistence
    when a large snapshot aborts the root transaction. Byte capping runs before
    acquiring the buffer write lock. Search updates publish only after root
    commit.
  • Catalog sources: separate SQL lifecycle hooks from wire options and give
    refresh waits and SQL requests their own deadline budgets. Catalog code
    receives the refresh budget from the client rather than importing coordinator
    internals.
  • Legacy result grid: generation-fence delayed rendering and pagination so
    an older page cannot replace the current result; expose recycled row indices
    to the stale-page regression test and document result-specific layout restore.
  • Request limiter: use a linked FIFO queue with constant-time cancellation
    of queued requests, preserving concurrency limits and ordering.
  • Recovery, toast and login UX: centred, danger-styled recovery UI; focus
    its heading and transfer focus into the recovered console; preserve Retry
    focus while busy; focus/describe sign-in errors; keep toast announcements
    available outside the SQL gate and during dialogs; use live theme tokens; warn
    both before and after token expiry; prevent incidental click dismissal;
    restore a dismissed warning on subsequent failures; provide expiry
    information, a sign-in action and Alt+N keyboard access. Renewal failures use
    consistent copy.
  • Test infrastructure and dependencies: add React 17 test-renderer and its
    types; extend unit and stubbed browser regressions; allowlist fake test tokens
    in gitleaks; update the QuestDB E2E submodule (26 commits, including
    permission-aware table visibility).
  • Enterprise CI: add .github/workflows/enterprise_e2e.yml, an opt-in Azure
    dispatch using vars.ENTERPRISE_E2E_ENABLED and secrets.ENT_DISPATCH_PAT.
    Fork PRs do not receive that secret. Private Enterprise source, binaries and
    live fixtures remain in the private repository. Dispatch success is not a
    passing integration result: check enterprise-e2e-web-console for the tested
    SHA. Setup and local execution are documented in e2e/README.md.

Review follow-up coverage

Review items Resolution
#1, #8, #23 Preserve confirmed script notifications; one shared finish path; settle LOADING once per script.
#2, #5, #6 Explicit not-sent renewal errors; no transient-failure logout threshold; retain pending 401 refresh recovery through its hard deadline.
#3, #9 Prepare byte-capped snapshots before the write transaction; retry metadata-only storage and warn after root abort.
#4, #7 Fence every live run generation; preserve error metadata; flush accepted throttled frames before session invalidation.
#10, #11 Reset confirmation state and invalidate waiting confirmations; clear shared credentials and cancel old telemetry timers.
#12–#15 Test current rotated tokens, loading-notification cancellation, root-commit search publication and deterministic loading cleanup.
#16–#20 Focused/centred recovery UI, actionable persistent toast with keyboard access, consistent renewal wording and simpler toast specificity.
#21, #22, #24–#28 Shared session handles/error guards; central authorization; pure token normalization; separate lifecycle options; remove unused helpers/reset hooks/payload fields; accurate local-error and commit-outcome types; clearer names/constants/comments.
#29 Document every shipped concern, the secret-using CI workflow and current validation results here, rather than describing this as only a refresh-loop change.

Validation

Validation of earlier PR head 908eba9dd43af4f7080599b4cb1f7760e2d65a67
(before the local six-issue follow-up):

  • yarn typecheck — passed.
  • yarn lint — passed, including color-token checks.
  • yarn build — passed (existing bundle-size advisory remains).
  • yarn test:unit — 2,925 tests passed in 153 files.
  • yarn cypress run --spec 'e2e/tests/auth/*.spec.js' --browser electron —
    57 passed against the production preview with stubbed HTTP responses:
    47 auth, 6 notification and 4 unauthorized-recovery tests.

Six-issue follow-up validated locally on 2026-10-16 and now published in
842d2545e84cd134c01fb719ed0b0ad71e3a3f81 (included in current PR head
75eae8bb9ccb3526cc3270eb38eb32a2f521ded1):

  • yarn typecheck, yarn lint and yarn build — passed.
  • yarn test:unit — 2,941 tests passed in 154 files.
  • yarn cypress run --spec 'e2e/tests/auth/*.spec.js' --browser electron —
    60 passed against the production preview with stubbed HTTP responses:
    50 auth, 6 notification and 4 unauthorized-recovery tests.

CI selector follow-up in 75eae8bb9ccb3526cc3270eb38eb32a2f521ded1:

  • Update the console editor regression to use the toast's current accessible
    name, Close notification, instead of the obsolete close selector.
  • Production build, targeted ESLint and Prettier checks — passed.
  • yarn cypress run --spec e2e/tests/console/editor.spec.js --browser electron
    completed with 122 tests passing and 1 pre-existing pending test. The local
    macOS Electron process then segfaulted during shutdown, so this was not a
    clean command exit. A focused rerun of the previously failing test passed
    with a clean exit against the real local QuestDB server.
  • New runs for the current head: CI
    and context-path tests.
    Their final results are not claimed by the local validation above.

The earlier 2,838-unit/38-auth figures described an older validation checkpoint,
not the current published head or the local follow-up. The full console E2E
suite and live Enterprise OIDC lane were not run locally. The Enterprise
rotation assertion was extended locally but remains unverified in the private
lane; the stubbed auth suite is not a substitute for live IdP/Enterprise tests.

When an OIDC token refresh failed at the transport level (token endpoint
unreachable, or a non-JSON response body), refreshTokenMethod() rejected
inside the setInterval callback of refreshAuthToken(), before the static
refreshTokenPending flag was reset. The flag was left stuck true, the outer
promise never settled, and every subsequent query deadlocked waiting on it —
the console silently froze with no 401 and no logout.

Wrap the refresh in try/catch/finally so refreshTokenPending is always reset.
On failure the stale token is kept in place and the next request receives a
401, which drives the existing re-auth flow — matching the behaviour when
there is no refresh token at all.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@glasstiger glasstiger added bug Something isn't working web-console Issues relevant to "web-console" package labels Aug 25, 2026
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Web Console deploy preview

Preview Commit Logs
https://pr-603--web-console.netlify.app 911d0b9 build log

@glasstiger glasstiger changed the title fix: prevent auth token refresh failure from deadlocking the client fix: keep Web Console responsive when SSO token refresh fails Sep 28, 2026
Comment thread e2e/tests/enterprise/oidc.spec.js Resolved
Comment thread e2e/tests/enterprise/oidc.spec.js Resolved
Comment thread e2e/tests/enterprise/oidc.spec.js Resolved
The OIDC refresh-failure E2E test reuses the fake token fixtures used throughout oidc.spec.js. Ignore the findings in 0f4d4ac and mark the lines with gitleaks:allow so the squash commit on main is not flagged either.
@glasstiger glasstiger changed the title fix: keep Web Console responsive when SSO token refresh fails fix: fix Web Console hanging when SSO token refresh fails Sep 29, 2026
glasstiger and others added 7 commits September 29, 2026 10:56
The legacy grid mounts only after the first result arrives, so asserting
.qg-viewport right after reloading with useNewGrid=0 always timed out.
Assert it after the first query instead.

Also release the old query's page before the new result loads that page.
The previous scroll away and back could refetch the page, hiding the stale
write, so the test only sometimes failed without the generation guard.
When the server reads the user's groups from the ID token
(acl.oidc.groups.encoded.in.token), the console authenticates with the
ID token, yet it timed the refresh by the token response's expires_in,
which describes the access token. Time it by the ID token's exp claim
instead, and fall back to expires_in only when the claim is unreadable.

If the identity provider did not return the ID token the server
expects, at sign-in or on refresh, the console sent "Bearer undefined",
and the resulting 401s ended the session with no explanation. Show a
"Missing ID token" error instead, and never install a missing bearer.

The OAuth callback params stayed in the URL after a failed sign-in, so
a reload resubmitted the used code, and its invalid_grant error
replaced the original one. Restore the URL whatever the outcome, which
also keeps a shared query link across a failed sign-in.

A token endpoint that could not be reached, returned a response that
is not JSON, or did not respond left the sign-in on a blank screen.
Show an error instead, and give up after 10 seconds, the same timeout
as the refresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
glasstiger and others added 3 commits October 14, 2026 01:28
A token response may omit expires_in, which OAuth only recommends. An
ID token bearer expires at its exp claim regardless, so it no longer
needs one. Without any known expiry, sign in anyway, but never refresh
the token, proactively or after a 401: such a provider typically issues
no refresh tokens either, so the token is used until the server rejects
it, and the user then signs in again.

A catalog request's 10s deadline kept running while the client waited
for the token refresh that follows a 401, so Query Activity and table
details could become unavailable even though the refresh succeeded.
The client now reports every refresh wait, and the catalog deadline
gives the refresh its own bounded time before restarting the SQL
timeout.

Modal dialogs hide the rest of the page from assistive technology,
except live regions, which hid the SSO refresh warning from screen
readers. Mount the notifications in a live region, as later
react-toastify versions do.

The refresh warning promised a retry, which does not happen while a
timed-out refresh request is still pending. State only the consequence.

The enterprise reload test returned expires_in: 0, which is not a valid
lifetime. Use 300 seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address the PR review findings across token renewal, script outcomes, notebook persistence, session teardown, accessibility, and auth types. Add regression coverage and document the complete PR scope.\n\nValidated with typecheck, lint, production build, 2,838 unit tests, and 38 stubbed auth browser tests.
Preserve never-started notebook run outcomes, reject stale headless results before serialization, and fix dialog and login focus handling.

Update persistent refresh warnings at token expiry, add regression coverage, and correct the PR description shortcut and fixture count.
Restore SSO refresh capability across provider remounts, recover schema after local renewal failures, and preserve no-send outcomes for queued headless reads.

Settle script loading independently of abort targets, defer accepted snapshot preparation until after session invalidation, and align headless cancellation metadata.

Add 14 regression tests. Validated with typecheck, lint, build, and 2,887 unit tests across 151 files.
Keep transient refresh failures recoverable after SQL 401 responses, preserve password form focus, and restore shortcut focus when closing notifications.

Simplify loading notification settlement to use only the query ID and extract the shared button base without tooltip dependencies. Add unit and browser regression coverage.
Prevent obsolete initial code exchanges from replacing recovered logins, coordinate recovery across mounted schema panels, and report validation-only notebook runs as never executed. Add regression coverage for all three review findings.
@glasstiger

Copy link
Copy Markdown
Contributor Author

Level-3 code review

Issues

No confirmed introduced defects found in PR #603, reviewed at d1badd7823bcdeacfa6c491cdc38e1539828ced9.

The level-3 review completed all 13 independent passes and a widened follow-up on unchanged callers. No broken out-of-diff contract was established.

False-positives

No draft findings were submitted for per-finding verification.

Two exploratory concerns were resolved during the caller follow-up:

Category Concern Disposition
Cross-context caller impact Legacy-grid layout hashing could finish asynchronously after result replacement. Dismissed: layoutStoreComputeKeyAndHash computes the hash and invokes its callback synchronously. Deferred rendering and pagination are generation-guarded.
Cross-context caller impact Late Schema/AI-action error toasts could survive logout and appear after re-login. Not established as an introduced defect. Toastify unregisters asynchronously, so synchronous logout followed by a promise catch does not prove cross-login queuing. Similar delayed-error paths existed in the baseline. Browser-level verification remains outstanding.

Summary

Verdict: approve, based on source review and passing local quality gates—not a completed CI or live-integration sign-off.

Local check Result
yarn typecheck Passed
yarn lint Passed
yarn build Passed; bundle-size advisory remains
yarn test:unit 2,954 tests passed across 155 files

Validation limits at review completion:

  • Public CI and context-path tests were still pending at the final review check.
  • Enterprise E2E dispatch was skipped; live IdP and token-rotation behavior remain unverified.
  • Cypress suites and screen-reader behavior were not exercised locally.
  • The E2E server fixture’s 24-commit advance was checked as test infrastructure, not independently audited as server code.

No introduced regression was confirmed across authentication, query execution, notebook persistence, notifications, catalog recovery, or grid replacement.

Totals: 0 confirmed findings; 0 draft findings requiring verification. 0 in-diff / 0 out-of-diff defects. No source files were changed.

glasstiger and others added 12 commits October 16, 2026 14:38
Resolve all 32 review findings covering refresh coordination, session isolation, keyboard access, schema recovery, notebook persistence, and regression coverage. Remove the stale PR draft and unrelated server bump.
Address the Moderate and Minor review findings for PR 603, including toast accessibility, CSV upload races, interruption history, session fencing, schema recovery, settings feedback, and non-renewing telemetry. Consolidate shared helpers and extend regression coverage.

Validated typecheck, lint, production build, 3,031 unit tests, and 64 stubbed Chrome browser-auth tests. Live Enterprise OIDC and manual screen-reader validation remain separate checks.
Propagate terminal query outcomes to accepted aliases and moved namespaces. Batch CSV status refreshes and include pending uploads in reauthentication interruption checks.

Restore grid selection after notification excursions and origin focus on toast message dismissal. Add unit and native-browser regression coverage.
Replace fixed rendering sleeps and zero-timeout snapshots with retryable DOM assertions. Wait for the measured scroll range before scrolling while retaining the 10,000-query virtualization, filter and sorting checks.
Include pending preference writes in sign-in interruption checks. Restore notification focus on actual dismissal and retain external origins across toasts.

Use an IndexedDB compound revision index for headless snapshot checks without loading result payloads. Add unit and browser regression coverage.
Option+N types ñ/ã/õ (as a dead key) or ń on Apple keyboards. While any
toast was visible, the notification shortcut consumed that key in Monaco
and other text fields and moved focus into the toast. On macOS and iPadOS
the shortcut now leaves Option+N to text fields and still works
elsewhere. Windows and Linux Alt+N, which types nothing, is unchanged. The
refresh warning tells Apple users to press it outside a text field.

The logout and "Sign in again" warnings counted console-issued reads as
user statements, so an expired session reported that statements may have
executed when nothing was run. Catalog, schema, monitoring, telemetry,
result paging, chart and notebook auto-refresh reads now pass a
background option that is never sent to the server. They are still
aborted when the session ends, but no longer count. "Sign in again" only
claims execution uncertainty when a user statement or write was sent.

Pin the platform in e2e tests that press Alt+N in text fields, so the
suite matches CI on macOS hosts, and add a macOS Option+N test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@glasstiger
glasstiger marked this pull request as draft October 20, 2026 10:32
glasstiger added a commit that referenced this pull request Oct 21, 2026
The three .gitleaksignore entries added here fingerprint lines 102-104 of
e2e/tests/enterprise/oidc.spec.js in commit 0f4d4ac. That commit is on
the #603 branch, not in this branch's history, and those lines hold no
token here. Every token this branch adds to oidc.spec.js is marked with
gitleaks:allow, so the entries suppressed nothing. #603 carries its own
copy of them.
glasstiger added a commit that referenced this pull request Oct 21, 2026
)

* fix: fix Web Console hanging or signing out when SSO tokens expire

Extracted from #603: the console half of questdb/questdb#7799 and
questdb/questdb-enterprise#1267. Those PRs stop serving a request from the
qdb_session cookie when its Authorization header is rejected, so an expired
or stale bearer now gets a 401 and closes the session instead of being masked.

- Refresh an ID-token bearer (acl.oidc.groups.encoded.in.token=true) by the
  ID token's exp claim rather than the access token's expires_in, and report
  a missing ID token instead of sending "Bearer undefined".
- Read the bearer at send time for SQL, validation, CSV check/import and
  preference requests, and never let stored REST/Basic credentials replace
  an SSO bearer.
- Coordinate token refresh in one bounded, cancellable place: queries wait up
  to 10s, a rotating refresh may still land within 60s, transient failures
  back off without logging out, and an expired bearer is never sent after a
  failed renewal (fixes the refresh deadlock that froze every query).
- Recover from a rejected bearer: refresh and resend once (a 401 executed no
  SQL), ignore 401s answered for an older bearer, and log out only when the
  current bearer is rejected.
- Show a renewal warning and a recovery view with Retry/Sign in again instead
  of a blank console, and report sign-in failures.
- Share one client between the console and telemetry so telemetry never sends
  a stale bearer, and mark read-only requests as background so logout warnings
  only count statements and writes.
- Dispatch the Enterprise Web Console e2e lane added by
  questdb-enterprise#1267, send the stored REST token in e2e execQuery, and
  extend the unit and stubbed browser auth tests.

* update submodule

* fix: keep uploads, the schema tree and notebook results intact when the session ends

- Let uploads and settings saves that are already being sent finish when
  the session ends, instead of aborting them mid-stream: an aborted CSV
  upload can leave a partially imported table. Their results are still
  rejected as session-ended and no longer count as the next login's
  activity. Queries, and writes still waiting for renewal, are cancelled.
- Show a failed SSO renewal in the Schema panel and reload it once a
  request succeeds, instead of leaving the tree blank.
- Supersede notebook cell runs, auto-refresh rounds and headless agent
  runs before session end aborts their requests. Their aborted results
  no longer overwrite the last good snapshot, save refresh errors, delete
  chart snapshots or record a possibly committed write as a failed run.
- Cover the session-end client behaviour, Schema recovery, notebook
  session end and run_query's unverified classification, and add an e2e
  test for renewing and resending an editor statement once after a 401.

* test: test SSO 401 recovery against a real Enterprise server

Every 401 in the auth e2e tests was stubbed, so nothing checked the tandem
server change (questdb/questdb#7799, questdb/questdb-enterprise#1267)
against a real server.

The new Enterprise OIDC test leaves /exec unstubbed. After a reload, silent
re-auth installs a bearer the userinfo fixture rejects, while the browser
still sends the qdb_session cookie of the earlier sign-in. The test checks
that the server answers that session start with 401 instead of serving it
from the cookie, and that the console renews its token once and resends its
startup check once, then keeps working as the same user.

Document this coverage, and that ID-token bearers still need an Enterprise
lane with a fixture that signs ID tokens.

* update submodule

* update submodule

* ci: run the OSS submodule check on Python 3.12

"Check if OSS is on master" failed on runners with the ubuntu-26.04 image,
whose default Python is 3.14. pygit2 1.14.1 ships wheels only up to CPython
3.12, so uv built it from source and failed on the missing libgit2 headers
(git2.h). Runs on ubuntu-24.04 still passed.

Pin the interpreter so uv installs the prebuilt wheel on every image. The
check itself is unchanged; the e2e/questdb pointer is the OSS master head.

* fix: warn before leaving the page would cancel uploads still being sent

Ending a session lets uploads and settings saves that are already being
sent finish, because aborting a CSV upload mid-stream can leave a
partially imported table. But "Sign in again" told users those uploads
"may still finish", and the Login view's next step, signing in with SSO,
navigates away and cancels them.

- Keep counting sent writes after their session ends, until they settle.
- Show that count on the Login view, and ask the browser to confirm
  leaving the page (SSO redirect, reload or closing the tab) until they
  finish. Logging out from the toolbar, which reloads at once, is
  unchanged.
- Say in the "Sign in again" confirmation that uploads continue only
  while the tab stays open.

* test: test startup 401 recovery in the required auth e2e lane

The only check of the tandem server change (questdb/questdb#7799) against
a real server is in the Enterprise lane, which CI skips unless
ENTERPRISE_E2E_ENABLED is set. Replay its startup sequence with stubs, for
access-token and ID-token bearers: the server rejects the new bearer of
both the session start and the startup check, and the console renews its
token once, resends the check once and keeps the session.

* fix: keep CSV uploads and edits when a delayed table check returns

A table check can now wait up to 10s for a token refresh. The Import view
then replaced the whole file list with a copy taken before the wait. That
reset a running upload to "Upload", so a second click could import the file
twice. It also reverted edits and dropped files added meanwhile.

- Merge only the checked status into the current list, and ignore a status
  checked for a name the file no longer has.
- Apply renames and other edits at once, so an upload started during the
  check uses the new name. Only the status waits for the check.

* fix: stop a query rejected with 401 from waiting 60s for renewal

After a 401, a query waited for the token refresh until its 60s network
deadline. Queries waiting before their first request gave up after 10s,
when the console already reported the renewal as failed, so a rejected
statement kept running next to the failure warning.

After a 401, wait no longer than before the first request. The refresh
itself still runs to its network deadline, so a late rotated token is still
installed for the next request.

* fix: stop asking to sign in again on queries the old session interrupted

Ending the session without a reload rejects the queries still running with
"Your session has ended. Please sign in again." The editor and the AI chat
stored that as the query's notification, so after signing in again with a
password, the console still asked the user to sign in.

- Editor, script and AI chat runs now say that the query was not run, or
  that it was interrupted and may have executed. This also replaces the
  "Running..." notification, whose Stop button would otherwise start a query.
- A result page read interrupted by the session end adds no notification.

* fix: clear the Schema panel's renewal error once the tables load again

A failed SSO renewal is shown in the Schema panel. After the renewal
recovered and the tables loaded, a later unrelated failure still showed
"Could not renew your SSO session".

* fix: end the identity provider's renewal error as a sentence

Identity providers often omit the full stop, e.g. Keycloak's "Token is not
active", which read "Token is not active Please sign in again."

* fix: keep downloads working after the console recovers from a 401

A 401 for a rejected bearer closes the server's cookie session, and the
Enterprise server deletes the qdb_session cookie with every 401. The console
renewed its token and resent the query, but never asked for a new session.
Downloads authenticate only with that cookie, so CSV and Parquet exports
failed with "Unauthorized" until the page was reloaded.

After a 401, the next SQL request of the same SSO login now sends
session=true, one request at a time, until the server accepts one. The
server opens a session for it only when the browser sent no cookie, so the
parameter changes nothing while the cookie is still valid.

* fix: stop showing the old table's status for a renamed CSV import

Renaming the target table of a queued CSV applied the new name at once, but
kept the old name's status until the existence check answered. The check
can wait up to 10s for a token renewal, and a failed check never answered.
A file renamed to an existing table could show "Ready to upload", and its
upload appended to that table without the "Table already exists" warning.

The new name now shows "Checking table name…" and can't be uploaded until
its check answers. A failed check shows "Could not check table name"
instead of the old status.

* fix: keep the unfinished-upload warning after signing in again

Ending a session lets uploads and settings saves that are already being
sent finish, and Login asked before leaving the page until they did. But
signing in again with a password replaces Login without leaving the page,
so the notice and the leave-page prompt disappeared while the upload was
still being sent. A reload then cancelled it without asking, which can
leave a partially imported table, and the empty Import view invited
importing the same file again.

- Ask before leaving the page in every view, not only on Login, until the
  previous session's writes settle.
- After signing in again, show a warning with their live count in the
  console until they settle.
- Logging out from the toolbar still reloads at once. Asking there could
  keep a console without a session, as Login is not shown before the
  reload. The warning says that logging out cancels them.

* fix: stop an editor script cleanly when the session ends

Ending the session aborts a running script's statement and stops the
script before its next one, but nothing tested it, and the end of the run
failed: the editor is unmounted while the script settles, and revealing
the position of an editor without a model threw "Invalid arguments". The
run was never settled, so the editor stayed marked as running a script
and the query log missed its "Stopped after..." summary.

- Settle the run when the editor no longer has a model.
- Check for a stop before the first statement too, so a session that
  ends while the script waits to start sends nothing.
- Test both ways a session ends during a script, with stop after failure
  off: signing in again while a statement runs, and a 401 that signs the
  user out. The script stops before its next statement, and the log says
  whether the interrupted statement may have executed or was not run.

* chore: drop gitleaks fingerprints of a commit outside this branch

The three .gitleaksignore entries added here fingerprint lines 102-104 of
e2e/tests/enterprise/oidc.spec.js in commit 0f4d4ac. That commit is on
the #603 branch, not in this branch's history, and those lines hold no
token here. Every token this branch adds to oidc.spec.js is marked with
gitleaks:allow, so the entries suppressed nothing. #603 carries its own
copy of them.

* fix: reopen the cookie session after any 401, not only a query's

The server closes the session of a request whose bearer it rejects, and
Enterprise deletes the qdb_session cookie with every 401. The console
asked for a new session (session=true) only after an /exec 401. Live
validation, CSV checks, uploads and settings saves send the same cookie,
and validation runs while the user types. If one of them saw the 401,
and the bearer was renewed before any query was rejected, no query asked
for a session. CSV and Parquet exports, which authenticate with the
cookie alone, then kept failing.

Record the lost session whichever request saw the 401. While the cookie
is still valid, asking for a session changes nothing.

* fix: keep dropped CSV files when their table check fails

A table check can wait for a token renewal and then fail, e.g. after a
failed SSO renewal. Only a renamed file handled that. A dropped file was
silently lost: it never appeared, and no error was shown. And one failed
recheck, when the Import view became visible, discarded every other
file's new status.

- List a dropped file whose check fails as "Could not check table name",
  like a renamed one, and let it be uploaded.
- Keep a file for an existing table whose schema can't be read, without
  a pre-filled schema. QuestDB then infers it from the file.
- Keep a file's status when its recheck fails, and merge the others.
- Count dropped files as queued at once, so that dropping one again
  while its check waits is reported as a duplicate, not queued twice.

* fix: keep notebook snapshots when a refresh is not executed

After a failed SSO renewal the user stays signed in, but statements are
not executed. Auto-refresh rounds treated that like a failed query. A
chart's frame was replaced with errors and its saved snapshot deleted,
and a grid saved "Could not renew your SSO session" as a refresh error,
shown again after the next sign-in. Rounds cut off by the end of the
session are already dropped.

Treat a statement that was not executed like an aborted one. A chart
keeps its frame and its saved snapshot. One with nothing to show yet
shows the failure, but still keeps its snapshot. A grid slot keeps its
rows, and the round saves nothing.

* fix: stop presenting auth failures as SQL errors or unsent requests

A failed SSO renewal was marked in the editor like a SQL error: a red
marker on the query's first word, with the cursor moved there. A session
end already only replaced the "Running..." notification.

The messages also said "not sent" of requests that the server had
received and rejected with 401 or 403. What holds in every case, and
tells the user that a retry is safe, is that nothing was executed.

- Show a failed renewal in the notification only, for single runs and
  scripts, without marking the query.
- "Not executed: your previous session ended without running this
  query." replaces "Not run: your previous session ended before this
  query was sent."
- A failed renewal says "the request was not executed" instead of "the
  request was not sent", and the recovery view no longer says "No
  request was sent."

* fix: clear the last query result when the session ends

The store outlives the console: signing in again with a password
remounts it in the same document, without a reload. The result grid then
showed the previous login's last rows, possibly to another user, and
scrolling re-ran its query with the new credentials.

Clear the result when the session ends. Notifications stay: they tell
whoever signs in next what the end of the session interrupted. Also
correct the logout comment, which described a store reset that did not
happen.

* update submodule

* fix: renew the SSO session when Retry is pressed after a failure

After a failed SSO renewal, the recovery view's Retry did nothing for 5
to 30 seconds: the backoff between failed renewals refused it at once,
without a request, and the view looked unchanged. While a renewal that
had timed out was still waiting for the identity provider, for up to 50
seconds, Retry was refused the same way. The view still said "Retry to
renew it".

- Retry skips the backoff between failed renewals, which protects the
  identity provider from background requests. The throttle after a
  successful renewal still applies.
- If a renewal is still in flight, Retry waits for its answer instead of
  starting another, which could spend a refresh token the first has
  rotated. It shows "Reconnecting…" meanwhile.
- The recovery view no longer asks to wait before retrying. The renewal
  warning says that queries retry the renewal after a short wait. They
  keep the backoff, so that a script doesn't renew once per statement.

* fix: stop an edited chart drawing forever when it is not executed

A refresh round with a statement that was not executed, e.g. after a
failed SSO renewal, kept any frame the cell held, and didn't settle.
After an edit, that frame was for the old SQL, which the chart doesn't
draw. The cell showed "Drawing…" with no error until a later round
reached the server.

Keep the frame only if the chart draws it for the current statements,
using the same test as DrawCanvas. Otherwise show the failure, as for a
chart with no frame, and still keep the saved snapshot.

* fix: end the previous login when settings recovery shows Login

A failed settings refresh unmounts the console, and Retry mounts it
again without ending the tab's login. If another tab had logged out
meanwhile, removing the shared password credentials, Login was shown
while this tab still counted as signed in. The next sign-in then kept
that session: requests of the previous login could still complete into
it, and none of the session-end cleanup ran, so its last result stayed.

End the session before showing Login in that case, and explain the
interrupted requests as logout does. Stored credentials and the server
session are left alone.

* fix: close gaps around session end and failed SSO renewal

- An agent's run on a closed notebook was dropped whole when the session
  ended, so the cell kept its previous run on record, even for writes
  that had already committed, and they could be repeated. Only a run
  that has not launched is cancelled now. A launched run records the
  statements that completed, and the one in flight as interrupted, with
  its outcome unverified. Its remaining statements are not sent without
  the login's credentials, and are recorded as not executed. A saved
  result says what the end of the session did to the query, instead of
  asking to sign in again.
- A chart whose frame for its SQL had no rows to draw, e.g. the error of
  a run kept when the cell became a chart, still showed "Drawing…"
  forever after a round that was not executed. Keep only a frame that
  the chart draws.
- A CSV file dropped again while the table check of its first drop
  waited for a token refresh could still be queued twice: any change to
  the list, and showing the list for the first time, forgot the pending
  name. ImportCSVFiles now tracks the dropped files that are not listed
  yet, and the drop box counts them as queued.
- "Sign in again" removed the SSO session flag that other tabs share, so
  another SSO tab that reloaded showed Login instead of signing in
  silently. It now ends only this tab's session.
- When the session ended, the Schema panel took its interrupted columns
  load for an unsupported function, and sent the fallback query without
  credentials. It now stops. After a failed renewal it doesn't try the
  fallback either, as the server ran no query. Answers that arrive after
  the session ended are not stored.
- The tables and columns of the previous login stayed in the store after
  a session end without reload, and fed autocomplete, the AI assistant
  and CSV import for whoever signed in next. They are now cleared with
  the last result.

* update submodule

* fix: close more gaps around logout, session end and SSO renewal

- Ending the session of an SSO tab, by logging out, signing in again or
  after a 401, removed the password credentials that tabs share, so
  another tab signed in with a password showed Login once it reloaded.
  Only a tab signed in with a password removes them now. An SSO tab also
  keeps its login method, so that reloading it resumes SSO or shows
  Login, instead of signing in with another tab's password.
- Logging out from the toolbar reloads the page at once, which cancelled
  running statements and uploads being sent without asking, although a
  cancelled CSV upload can leave a partially imported table. It now asks
  first, and keeps the session if declined.
- Logging out from the toolbar also cleared the last result, tables and
  columns before the reload, blanking the console for a moment. The
  reload discards them anyway, so they are left to it.
- When the session ended, an agent's read-only run on a closed notebook
  recorded its reads as interrupted or not executed, replacing the
  previous run and its saved rows. A read-only run is now cancelled like
  one that has not launched, so the previous run stays on record. A run
  that writes still records what it did.
- Once the token had expired, a statement run again while a timed-out
  renewal still waited for the identity provider, for up to 50 seconds,
  failed at once without a new renewal, although the renewal warning
  says that running it again after a short wait retries the renewal.
  After the backoff, a statement the user runs now waits for that
  renewal to be answered or given up, and then renews. Background reads,
  and statements run during the backoff, still fail at once.
- The one-off telemetry config read at startup never renewed the
  session, so it failed near expiry or after a failed renewal, e.g. at
  every login with a token that lives under 30 seconds. Metrics then
  asked to enable telemetry, and console events were not tracked, until
  the next login. The read may now renew the session, like the console's
  other reads.
- If the request that opens the cookie session at SSO sign-in was
  rejected or could not be sent, downloads could fail for the whole
  login: only a later 401 made a request ask for a new session. The next
  SQL request now asks for one in that case, and a network error there
  no longer escapes as an unhandled rejection.
- A chart without a frame it could draw deleted its saved snapshot on
  the second round in a row that was not executed: that round matched
  the failure the first one showed, and an unchanged frame without rows
  cleared the snapshot. The snapshot now stays until a round reaches the
  server.
@glasstiger

Copy link
Copy Markdown
Contributor Author

superseded by #617

@glasstiger glasstiger closed this Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working security web-console Issues relevant to "web-console" package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant