Repository navigation
fix(client): give 401, 403 and 409 their own error kinds (GHY-4738) - #70
Merged
Merged
Conversation
…API key A 403 means Metabase accepted the key and refused the request. Metabase answers some validation errors that way with a text/plain reason, such as "A table with that name already exists." for a transform whose target table exists, and a permission refusal with "You don't have permissions to do that." Both read as "Invalid or unauthorized API key", which sent people and agents off debugging authentication. A 403's plain-text reason is now the message; a 401, whose body is only "Unauthenticated", and a 403 without one keep the key message. GHY-4738 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…usal A 401 is a credential Metabase did not accept; a 403 is a request it refused from a user it identified. A 403 without a plain-text reason still said "Invalid or unauthorized API key", so the right message depended on Metabase sending a body. It now reads as a refusal either way, and a reason, when present, makes it specific. GHY-4738 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
401 stays `auth`: Metabase did not accept the credential, so the message names the API key. 403 is `forbidden`, a request refused for a user Metabase identified, and 409 is `conflict`, a request that clashes with existing state; neither is about the key. Both show the server's reason and otherwise fall back to a default for their status. The dashboard card check treats `forbidden` like `auth`: the card is unreadable. GHY-4738 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… responder The 403 default named "the API key's user", which an OAuth login doesn't have, and both defaults said Metabase refused, though a proxy in front of it can answer too. They now say the request was refused, with the status. GHY-4738 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
verify read 401 and 403 from /api/user/current alike as "auth", so `mb auth list` said to update the token. Metabase serves every identified user their own record; a 403 there comes from whatever answered instead. verify now takes the client's own kind: `auth` (401) is a credential problem, anything else a server one. The stored failure kinds don't change. GHY-4738 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alxnddr
approved these changes
Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes GHY-4738: a transform on an existing table fails with "Invalid or unauthorized API key".
Why.
mbtreated every 401 and 403 as a bad key. Only a 401 is: Metabase didn't accept the credential (a bogus or missing key gets 401). A 403 means Metabase identified the user and refused. A 409 means the request conflicts with existing state; metabase/metabase#83799 makes "A table with that name already exists." a 409.Fix (1 file).
packages/client/src/http/errors.ts: each status gets its own kind; the status decides, and the server's text only makes a message specific.authInvalid or unauthorized API key (host: …).(or the server's JSON message)forbidden(new)conflict(new)Fallout.
core/auth/verify.ts:mb auth listmarks a profile auth-failed only on a 401; a 403 from/api/user/currentcomes from a proxy and reads as a server problem (stored kinds unchanged).resources/dashboard.ts: an unreadable card isauthorforbidden.index.test.ts: the exhaustive kind switch gains the two kinds.errors.test.ts: per-status kind and message tests.tests/e2e/profiles.e2e.test.ts: the limited-key 403 reads "You don't have permissions to do that.". Client README lists the kinds.Verified.
bun run checkpasses (1786 tests). Live against Metabase master with the ticket's repro:mainsaysInvalid or unauthorized API key (host: localhost:23000)., this branch saysA table with that name already exists.A bogus or missing key still gets 401 there, so the invalid-key e2e expectations hold.🤖 Generated with Claude Code