Skip to content

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

Draft
skyrpex wants to merge 1 commit 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 1 commit 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

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant