fix(cli): stop masking auth errors as token expiry#2945
Merged
migmartri merged 1 commit intochainloop-dev:mainfrom Apr 1, 2026
Merged
fix(cli): stop masking auth errors as token expiry#2945migmartri merged 1 commit intochainloop-dev:mainfrom
migmartri merged 1 commit intochainloop-dev:mainfrom
Conversation
093b98b to
948c8f5
Compare
948c8f5 to
6dd81dd
Compare
The Kratos errors.Is() function matches only on Code and Reason (not Message). Since all JWT middleware errors share Code=401 and Reason="UNAUTHORIZED", every authentication error was incorrectly matching ErrTokenExpired and showing "your authentication token has expired" regardless of the actual cause. Fix by removing the errors.Is() short-circuit in isWrappedErr and keeping only the Code+Message comparison, which carries the distinguishing text for each JWT error type. Also add explicit case branches for ErrTokenInvalid and ErrTokenParseFail, and a fallback for unmatched 401 errors that surfaces the server's original message. Closes chainloop-dev#2922 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Vibhav Bobade <vibhav.bobde@gmail.com>
6dd81dd to
9be4c21
Compare
migmartri
approved these changes
Apr 1, 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.
Summary
isUnmatchedAuthErrfallback does not shadow org-membership error casesisWrappedErrandisUnmatchedAuthErrhelper functionsRoot Cause Analysis
The Kratos
errors.Is()implementation only comparesCodeandReasonfields:All JWT middleware errors (
ErrTokenExpired,ErrTokenInvalid,ErrTokenParseFail,ErrMissingJwtToken) are created witherrors.Unauthorized("UNAUTHORIZED", "<message>"), giving them identicalCode: 401andReason: "UNAUTHORIZED"fields. Only theMessagefield differs. This causederrors.Is()to match any auth error againstErrTokenExpiredfirst in the switch statement, masking the real error type.Changes
errors.Is()short-circuit inisWrappedErr— keep only theCode + Messagecomparison, which correctly distinguishes between different JWT error typesErrTokenInvalid("token is invalid") andErrTokenParseFail("failed to parse token") so users see accurate error messagesisUnmatchedAuthErr) for unmatched 401 errors that surfaces the server's original message instead of silently dropping itIsUserNotMemberOfOrgErrorNotInOrg,IsUserWithNoMembershipErrorNotInOrg) come BEFORE the genericisUnmatchedAuthErrfallback. Note: org-membership errors use gRPC code 7 (PermissionDenied), not code 16 (Unauthenticated), so shadowing is structurally impossible — but the ordering is kept for readability/clarityisWrappedErrdocumenting why we use Message comparison instead of Kratoserrors.Is()isWrappedErrcorrectly matches each JWT error sentinel against itselfisWrappedErrdoes NOT cross-match different JWT error typesisUnmatchedAuthErrcatches generic 401/Unauthenticated errorsisUnmatchedAuthErrdoes NOT catch non-401 errors (PermissionDenied, Internal, NotFound, OK)errors.Is()masking bug and proving our fix avoids itisWrappedErrstill works correctly (tests the full server-to-client error path)Messagestrings of all four JWT sentinels (includingErrTokenParseFail's trailing space). If Kratos updates and changes these strings, the test fails immediately and alerts us to update our matchingTest Plan
go test ./app/cli/ -v— all 23 test cases pass (5 test functions)go vet ./app/cli/...passesFixes #2922