Skip to content

Show a helpful error when the plan does not include snapshots - #425

Draft
anisaoshafi wants to merge 2 commits into
mainfrom
devx-1009-lstk-load-gives-confusing-404-for-freemium-plan
Draft

Show a helpful error when the plan does not include snapshots#425
anisaoshafi wants to merge 2 commits into
mainfrom
devx-1009-lstk-load-gives-confusing-404-for-freemium-plan

Conversation

@anisaoshafi

@anisaoshafi anisaoshafi commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

On a plan without snapshots, every lstk snapshot command failed with a raw status code. Two different signals were leaking through:

✗ LocalStack returned status 404:
✗ list snapshots: failed to list cloud pods: status 403: {"error": true, "message": "generic.forbidden"}

Now, for all of them:

Error: Snapshots require a paid LocalStack plan
  Your plan does not include the snapshot feature.
  ==> Compare plans: https://www.localstack.cloud/pricing
  • Emulator requests (save, load, load --dry-run, remove, list s3://…): the emulator answers with a bare 404 and no body when snapshots aren't licensed. All 11 client methods now recognise that and return ErrSnapshotFeatureUnavailable, rendered through one shared message. save had no error translation at all before.
  • Platform requests (list, show): the platform signals the same thing as a 403, now mapped to api.ErrCloudPodsForbidden and rendered identically. Only 403 maps — a rejected token is a 401 and stays generic, so a re-login problem is never reported as a billing problem.
  • Any other empty-bodied failure no longer renders a dangling ": ".

Scope: snapshot commands only. lstk reset shares the state-reset request with load --merge=overwrite, so its error stays generic ("feature not available on this plan") rather than naming snapshots; giving it its own paid-plan message is a follow-up.

lstk decides this from the response, not from the cached license: the license's product list is coarse and the snapshot entitlement string isn't verifiable, so checking it locally risks blocking paying customers. Trade-off — lstk load still starts the emulator before failing.

Verified against a real freemium account

Every command below was run against a live emulator on a freemium license, not just mocks. All seven emulator paths and both platform paths render the new message; lstk reset correctly stays generic; exit code is 1 throughout.

Raw probes confirmed the premise: all six pods routes and state/reset return HTTP 404 with a 0-byte body — byte-identical to a control route that doesn't exist.

Testing this way is what surfaced the 403: it only reproduces against the real platform, so the original mock-only version of this PR shipped a half-fix that left snapshot list broken.

Automated coverage: all 11 emulator requests translate the empty 404, while a 404 carrying a message stays generic (keeps the check narrow); 403 vs 401 covered on the platform side; integration tests for load and save, both verified to fail before the fix.

Review: advisable — new user-facing wording, and the emulator half keys on the shape of a response rather than an explicit signal. A server-side 402/403 with a reason would remove that inference entirely and is worth a follow-up on localstack-pro.

Closes DEVX-1009

Co-Authored-By: Claude noreply@anthropic.com

🤖 Generated with Claude Code

Closes DEVX-1009

Co-Authored-By: Claude <noreply@anthropic.com>
@anisaoshafi anisaoshafi added semver: patch docs: skip Pull request does not require documentation changes labels Jul 31, 2026
The platform reports an unentitled plan as a 403 rather than the emulator's
empty 404, so these two commands still leaked a raw status and JSON body.

Co-Authored-By: Claude <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