Skip to content

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

Merged
anisaoshafi merged 3 commits into
mainfrom
devx-1009-lstk-load-gives-confusing-404-for-freemium-plan
Aug 4, 2026
Merged

Show a helpful error when the plan does not include snapshots#425
anisaoshafi merged 3 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
Before After
imagen imagen
  • 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

@anisaoshafi anisaoshafi added semver: patch docs: skip Pull request does not require documentation changes labels Jul 31, 2026
@anisaoshafi
anisaoshafi force-pushed the devx-1009-lstk-load-gives-confusing-404-for-freemium-plan branch from 9151984 to 74e77d7 Compare August 3, 2026 14:24
@anisaoshafi
anisaoshafi marked this pull request as ready for review August 3, 2026 14:47
@anisaoshafi
anisaoshafi requested a review from a team as a code owner August 3, 2026 14:47

@skyrpex skyrpex left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm halfway reviewing the PR, good job! I have some concerns though:

  • The detection used to know whether sandboxes are enabled or not is very brittle, as both share a response with a 404 status code and the only difference is a response with body text or not.

I wonder if we should, on top of your work, request a better mechanism from the LocalStack backends to detect whether features are enabled or not.

@anisaoshafi

Copy link
Copy Markdown
Collaborator Author

I'm halfway reviewing the PR, good job! I have some concerns though:

* The detection used to know whether sandboxes are enabled or not is very brittle, as both share a response with a 404 status code and the only difference is a response with body text or not.

I wonder if we should, on top of your work, request a better mechanism from the LocalStack backends to detect whether features are enabled or not.

Good thinking @skyrpex 🙏🏼
I had the same thought. I'm aware this is brittle, but this shouldn't break anything, but might at worst return a misleading error to the user (that was my testing and intention).

The proper response should come from the emulator backend, I'm going to follow up on that with snapshot feature owners. Even if we fix this behaviour on the emulator side, we would still want to rely on this rule for compatibility for users stuck using an old emulator version. I think this is still an improvement. What do you think?

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid, real-account-verified fix that collapses the raw status 404/403 leaks into a single "requires a paid plan" message across every snapshot path.

  1. thought: the empty-body-404 discriminator in internal/emulator/aws/client.go is inherently reactive — a reverse proxy or gateway in front of an external --endpoint-url emulator that answers a bare 404 would be surfaced as "requires a paid plan". You've documented this tradeoff (and the GET /_localstack/pods/{name}/versions bare-404 footgun) thoroughly in internal/snapshot/CLAUDE.md; the durable fix is a server-side 402/403 with a reason, which is worth the localstack-pro follow-up you noted. Non-blocking.
  2. praise: TestSnapshotEndpointsTranslateEmptyBody404 covering all 11 gated client methods with the paired narrow-discriminator guard, the 403-only-not-401 platform mapping so a re-login never reads as a billing problem, and integration tests that fail before the fix — exactly the coverage this class of change needs.

Automated review on behalf of @gtsiolis.


Generated by Claude Code

@anisaoshafi
anisaoshafi requested a review from skyrpex August 4, 2026 09:09

skyrpex commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Agreed, thanks!

anisaoshafi and others added 3 commits August 4, 2026 12:15
Closes DEVX-1009

Co-Authored-By: Claude <noreply@anthropic.com>
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>
@anisaoshafi
anisaoshafi force-pushed the devx-1009-lstk-load-gives-confusing-404-for-freemium-plan branch from 74e77d7 to 84d0476 Compare August 4, 2026 10:16
@anisaoshafi
anisaoshafi enabled auto-merge (squash) August 4, 2026 10:21
@anisaoshafi
anisaoshafi merged commit 0b557da into main Aug 4, 2026
19 checks passed
@anisaoshafi
anisaoshafi deleted the devx-1009-lstk-load-gives-confusing-404-for-freemium-plan branch August 4, 2026 10:28
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.

2 participants