Skip to content

Surface typed CLIErrors from auth.Login for known failures#985

Open
nimeshk03 wants to merge 3 commits into
wso2:mainfrom
nimeshk03:main
Open

Surface typed CLIErrors from auth.Login for known failures#985
nimeshk03 wants to merge 3 commits into
wso2:mainfrom
nimeshk03:main

Conversation

@nimeshk03
Copy link
Copy Markdown

@nimeshk03 nimeshk03 commented Jun 1, 2026

Purpose

auth.Login returned plain fmt.Errorf-wrapped errors on all failure paths, so cmd/login.go could not distinguish a known auth failure from a transport error — both became CLI_TRANSPORT.

Resolves #786

Goals

  • Map HTTP 401 and invalid_grant token-endpoint errors to clierr.Unauthorized
  • Map user-cancelled PKCE flow (context cancellation, stdin EOF) to a new clierr.AuthLoginCancelled code
  • Pass typed clierr.CLIError values through cmd/login.go unchanged instead of always wrapping as CLI_TRANSPORT

Approach

  • Add classifyTokenError in auth/login.go that inspects *oauth2.RetrieveError and returns a typed CLIError for known cases; returns nil for anything else so the existing fmt.Errorf wrap still applies
  • In loginPKCE, check for context.Canceled, context.DeadlineExceeded, and io.EOF before calling classifyTokenError
  • In cmd/login.go, use errors.As to pass through a clierr.CLIError from auth.Login before falling back to the CLI_TRANSPORT wrap
  • Add AuthLoginCancelled = "AUTH_LOGIN_CANCELLED" constant to clierr

User stories

As a script author using amctl login, I want the JSON error code field to distinguish auth rejections from transport failures so I can handle each case without parsing message strings.

Release note

amctl login now emits UNAUTHORIZED for 401/invalid_grant errors and AUTH_LOGIN_CANCELLED when the browser login is cancelled, instead of always emitting CLI_TRANSPORT.

Documentation

N/A — internal CLI error code change, no user-facing doc impact.

Training

N/A

Certification

N/A

Marketing

N/A

Automation tests

  • Unit tests
    • pkg/auth/login_test.go (new): 8 table-driven cases for classifyTokenError covering 401, invalid_grant, wrapped errors, 403, unknown codes, plain errors, nil response
    • pkg/cmd/login_test.go (new): 4 cases verifying typed CLIError passthrough, plain errors become CLI_TRANSPORT, cancelled login passes AuthLoginCancelled, missing --url returns INVALID_FLAG
  • Integration tests
    • N/A

Security checks

Samples

N/A

Related PRs

#753 — original amctl CLI PR where this fix was deferred

Migrations (if applicable)

N/A

Test environment

Ubuntu 26.04, Go 1.26.0

Learning

N/A

Summary by CodeRabbit

  • Bug Fixes

    • Improved classification and clearer JSON error codes/messages for OAuth/login failures (unauthorized, invalid credentials).
    • Browser-based login cancellations are now detected and reported with a dedicated error code.
    • Authentication failures are rendered consistently in command output.
  • Tests

    • Added unit and command tests validating the new login error classifications and JSON error output.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 17e57b5c-2198-4676-9450-3d0a4182d06d

📥 Commits

Reviewing files that changed from the base of the PR and between 709e206 and 269cef5.

📒 Files selected for processing (2)
  • cli/pkg/auth/login.go
  • cli/pkg/cmd/login_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • cli/pkg/cmd/login_test.go
  • cli/pkg/auth/login.go

📝 Walkthrough

Walkthrough

Classifies OAuth token-exchange and PKCE cancellation errors into typed clierr.CLIError values and surfaces those typed errors through the login command rendering instead of wrapping all failures as Transport.

Changes

OAuth Error Classification and Command Rendering

Layer / File(s) Summary
Auth layer error classification
cli/pkg/auth/login.go, cli/pkg/clierr/clierr.go, cli/pkg/auth/login_test.go
Adds classifyTokenError and wrapTokenError to detect oauth2.RetrieveError and map 401/invalid_grant to clierr.Unauthorized; loginClientCredentials and loginPKCE use the wrapper; loginPKCE maps context cancellation/EOF to clierr.AuthLoginCancelled; new constant AuthLoginCancelled added; unit tests added.
Command-level error rendering
cli/pkg/cmd/login.go, cli/pkg/cmd/login_test.go
runLogin now renders returned clierr.CLIError directly (preserving typed error codes) and falls back to clierr.Transport only for untyped errors; tests validate JSON error envelope codes for typed and untyped cases and early-invalid-flag behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through OAuth's tangled streams,
Mapping 401s and lost grants into named dreams.
When browsers close early, I thump and I call—
"AuthLoginCancelled" — typed for one and for all! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: surfacing typed CLIErrors from auth.Login for known failures. It is specific, concise, and clearly summarizes the primary objective of the changeset.
Description check ✅ Passed The PR description comprehensively addresses all template sections with concrete details: clear purpose and issue link (#786), specific goals mapped to implementation, detailed approach with code patterns, user story, release note, security confirmations, test coverage breakdown, and test environment specifications.
Linked Issues check ✅ Passed All three coding requirements from issue #786 are met: (1) auth/login.go maps 401/invalid_grant to Unauthorized and cancellation to AuthLoginCancelled via classifyTokenError, (2) cmd/login.go uses errors.As to pass through CLIError unchanged, (3) new AuthLoginCancelled constant added to clierr.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objectives: auth error classification helpers, typed error constants, PKCE cancellation detection, and cmd/login.go error handling. Test additions directly validate the implemented functionality with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@jhivandb jhivandb left a comment

Choose a reason for hiding this comment

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

/simplify cleanup notes

Comment thread cli/pkg/cmd/login.go Outdated
Comment on lines +108 to +111
var ce clierr.CLIError
if errors.As(err, &ce) {
return render.Error(opts.IO, scope, ce)
}
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.

render.Error already does this. asCLIError (render.go:107-120) and textError (render.go:86-89) both run errors.As(err, &cliErr) — typed CLIErrors pass through with their code, everything else falls back to Transport. This block plus the new errors import collapses to return render.Error(opts.IO, scope, err) with identical behavior.

Copy link
Copy Markdown
Author

@nimeshk03 nimeshk03 Jun 2, 2026

Choose a reason for hiding this comment

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

Addressed in the latest commit. Removed the explicit CLIError handling from runLogin and delegated error rendering directly to render.Error(...).

Comment thread cli/pkg/auth/login.go Outdated
Comment on lines 81 to 84
if ce := classifyTokenError(err); ce != nil {
return nil, ce
}
return nil, fmt.Errorf("client_credentials token exchange: %w", err)
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.

The classify-then-fmt.Errorf pattern is duplicated in loginPKCE (line 160). Could fold into one wrapTokenError(err, ctx) helper so each site becomes a single return nil, wrapTokenError(err, "client_credentials").

Copy link
Copy Markdown
Author

@nimeshk03 nimeshk03 Jun 2, 2026

Choose a reason for hiding this comment

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

Addressed in the latest commit. Extracted the classification-and-wrapping logic into wrapTokenError() and updated both login flows to use the helper. The PKCE path retains its existing fallback error context.

Comment thread cli/pkg/cmd/login_test.go Outdated
"github.com/wso2/agent-manager/cli/pkg/iostreams"
)

func TestRunLogin_TypedErrorPassthrough(t *testing.T) {
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.

These 4 tests each repeat ~13 lines of setup + envelope-decode + code assertion. auth/login_test.go in this same PR is already table-driven — a table here would cut the duplication and match that style.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in the latest commit. I consolidated the repeated login error test cases into a single table-driven test to reduce duplication and keep the assertions in one place.

@nimeshk03 nimeshk03 marked this pull request as draft June 2, 2026 11:38
@nimeshk03
Copy link
Copy Markdown
Author

Hi @jhivandb,

Thank you for the review and feedback.

I've addressed all comments in the latest commit.

Let me know if you have any further feedback or if there are any additional changes required.

@nimeshk03 nimeshk03 marked this pull request as ready for review June 2, 2026 11:55
@jhivandb
Copy link
Copy Markdown
Contributor

jhivandb commented Jun 4, 2026

Hi @nimeshk03, I don't think we should merge this right now.

The existing token exchange paths already preserve the OAuth error text:

return nil, fmt.Errorf("client_credentials token exchange: %w", err)
return nil, fmt.Errorf("authorization code exchange: %w", err)

So this mainly changes the structured JSON code from CLI_TRANSPORT to UNAUTHORIZED. That would be useful if we had a stable automation path depending on amctl login --json, but client-
credentials support is still minimal and the upcoming authz changes are going to change this area anyway.

I also don't see a real browser-cancel path that reaches AUTH_LOGIN_CANCELLED. Closing the browser tab doesn't call the callback, and an OAuth error redirect comes through as authorization error: ..., not context.Canceled, DeadlineExceeded, or io.EOF.

Given that, I don't see enough value in adding new stable error codes here yet. I think we should defer this until the authz/client-credentials story is settled, then add the error contract
around the final flow.

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.

amctl: Surface typed clierr.CLIError from auth.Login instead of always wrapping to Transport

2 participants