Skip to content

fix(license): recover from stale tokens and cached licenses without manual logout#394

Draft
skyrpex wants to merge 7 commits into
mainfrom
claude/lstk-auth-retry-flow-45e1af
Draft

fix(license): recover from stale tokens and cached licenses without manual logout#394
skyrpex wants to merge 7 commits into
mainfrom
claude/lstk-auth-retry-flow-45e1af

Conversation

@skyrpex

@skyrpex skyrpex commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes DEVX-658: after buying/upgrading a license, lstk could keep failing license validation until the user ran lstk logout by hand. The CLI now identifies why the license check failed and recovers on its own where possible.

The four behaviors

1. Rejected token → offer re-login and retry (interactive)

When the license server definitively rejects the token (HTTP 400/401/403) — e.g. the stored token predates a license purchase — lstk now asks:

Log in again to refresh your credentials? (Press ENTER to log in again)

Pressing ENTER drops the old token, runs the normal browser login, and retries the start once with the fresh token. This replaces the manual lstk logout && lstk login dance.

2. Rejected token → actionable error (non-interactive)

Same rejection in CI / piped mode can't open a browser, so lstk prints a structured error telling the user exactly what to do (lstk logout && lstk login, or set a valid LOCALSTACK_AUTH_TOKEN) instead of a raw error string on stderr. In both modes the rejection also deletes the cached license.json, since the server just told us it's no good.

3. License server outages no longer block the start

A 5xx from the license server or a 407 from a corporate proxy is not a verdict on the license — but it used to fail the start. These now degrade the same way an unreachable server already did: skip the pre-flight with a warning and let the emulator validate the license itself once it starts. Only real rejections (400/401/403) stay fatal.

4. Stale cached license → refresh and retry automatically

The pre-flight is skipped when the image is already local (offline support), which means a previously cached license.json gets mounted as-is. If that cache predates a plan change, the emulator exits with a license error on every run — the exact "stuck after upgrade" report. lstk now detects the license failure in the container logs, drops the stale cache, re-fetches the license (forced, even though the image is local), and retries the start once:

License rejected at startup — refreshing the cached license and retrying

No retry loop: if the license was freshly fetched this run, a startup license failure is a real verdict and surfaces as before.

Implementation notes

  • isDefinitiveLicenseRejection (400/401/403) gates fatal vs. degrade in validateLicense; resolves the "known limitation" comment about 5xx/407.
  • auth.Relogin = delete stored token + cached license + rerun login flow.
  • startWithLicenseRetry owns the mount + start + stale-cache retry; licenseStartupError classifies license-related container exits from the buffered startup logs.
  • StartOptions.AuthOptions threads auth.WithBrowserOpener into the internally built auth.Auth so tests never open real browser tabs.

Testing

  • Unit: status-code taxonomy, cache deletion on rejection, log classification, retry/no-retry matrix (startWithLicenseRetry), Relogin.
  • Integration: TestLicenseRejectionOffersReloginAndRetries drives the full interactive PTY flow (rejection → prompt → ENTER → login → retried start against Docker); existing license failure tests updated for the new structured error output.
  • Manually verified against the built binary: interactive re-login (single retry, second rejection stays fatal), non-interactive error block, and 502 degradation.

Fixes DEVX-658

🤖 Generated with Claude Code

skyrpex and others added 2 commits July 22, 2026 15:19
…anual logout

A rejected token or a stale cached license.json previously required a
manual `lstk logout` before the next start could succeed (DEVX-658).

- Definitive license rejections (HTTP 400/401/403) now delete the cached
  license.json, and interactive starts offer an in-place re-login
  (auth.Relogin) followed by one retried start with the fresh token.
  Non-interactive starts emit an actionable ErrorEvent instead of a raw
  stderr error.
- Non-definitive license server responses (5xx outages, 407 from
  corporate proxies) no longer block the start; the pre-flight degrades
  and the emulator validates the license at startup, like transport
  failures already did.
- When the container exits with a license failure while a cached
  license.json that this run did not refresh was mounted (pre-flight
  skipped, e.g. image already local), the cache is dropped, re-validated
  against the license server, and the start is retried once.

Fixes DEVX-658

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The structured license-rejection error dropped the product:version
context the previous raw error carried. Thread it through a typed
licenseRejectedError so the ErrorEvent title reads
"License validation failed for localstack-pro:2026.7.0".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@skyrpex
skyrpex force-pushed the claude/lstk-auth-retry-flow-45e1af branch from 55539dc to c8ded92 Compare July 22, 2026 13:23
@skyrpex skyrpex added semver: patch docs: skip Pull request does not require documentation changes labels Jul 22, 2026
skyrpex and others added 5 commits July 22, 2026 15:43
The retried start pulls the image, runs a login round-trip, and boots the
emulator, which overran the default 2-minute test context on CI runners —
and the interactive startup monitor's 20s "keep waiting?" prompt paused
the boot with nobody answering it. Use a 6-minute context and override
LSTK_STARTUP_TIMEOUT so only the prompts under test need answering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The retried start times out on CI with the emulator running but never
healthy, and the PTY transcript alone cannot explain why. Capture the
container state, its log tail, and a health-endpoint probe before
cleanup removes the container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in test

Diagnostics showed the emulator healthy within seconds while lstk waited
forever: the interactive post-start AWS profile setup asked its Y/n
question (the CI runner's ~/.aws has no matching profile) and nothing
answered it. Decline it from a watcher, since the prompt is skipped
entirely when the profile already matches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…yring

Reproduced the CI failure in a headless Linux container: the re-login
stores the token fine, but the file-keyring fallback writes it next to
the active config file (ConfigDir follows --config), which is this
test's temp dir — not the default config dir the shared
GetAuthTokenFromKeyring helper reads. Assert against the actual storage
location per backend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"License validation failed for localstack-pro:2026.7.0: invalid, ..."
reads as one sentence; the summary-line split put an odd newline between
the title and the reason.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs: skip Pull request does not require documentation changes semver: patch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant