Skip to content

feat(cli): show authentication source on auth errors#321

Merged
drappier-charles merged 3 commits into
mainfrom
cdrappier/devin/auth-source-hint-on-error
Jun 11, 2026
Merged

feat(cli): show authentication source on auth errors#321
drappier-charles merged 3 commits into
mainfrom
cdrappier/devin/auth-source-hint-on-error

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

When a CLI command hits a 401/403, the user now sees where the credentials came from — making stale or mismatched credential mistakes immediately obvious (context: a user had a stale BL_API_KEY env var for the wrong workspace and didn't notice the CLI was using it).

Works for all auth modes (API key, access token, client credentials) and all error paths (including bl get which bypasses PrintError).

Before:

Resource Model error: GET "https://api.blaxel.ai/v0/models": 401 Unauthorized {"code":401,"error":"Unauthorized"}

After:

Resource Model error: GET "https://api.blaxel.ai/v0/models": 401 Unauthorized {"code":401,"error":"Unauthorized"}
⚠ Authentication is using access token from credentials file (~/.blaxel/config.yaml) for workspace "charlou-dev"

Or when an env var is in play:

⚠ Authentication is using API key from environment variable BL_API_KEY
  If this is unexpected, unset the variable with 'unset BL_API_KEY' or update its value.

How it works

  • cli/core/auth_source.goResolveAuthSource(workspace) mirrors the SDK credential resolution: config-file creds win over env vars; if no config creds exist, env vars are used. IsAuthError(err) detects 401/403 via SDK type or string matching.
  • ExitWithError (the universal exit point) calls PrintAuthSourceHint() on auth errors — this covers paths like bl get that use fmt.Printf directly.
  • PrintError also calls the hint for paths that go through it.
  • A authHintPrinted flag deduplicates when both are on the same path.

Link to Devin session: https://app.devin.ai/sessions/aea94031def046a2a32308b4534c250f
Requested by: @drappier-charles


Note

Adds authentication source hints to CLI error output on 401/403 failures, showing users whether credentials came from environment variables or config files. The latest commit addresses the previous review feedback about false-positive substring matching.

Written by Mendral for commit 035a11a.

When a 401/403 error occurs, the CLI now prints a hint showing where
the credentials came from (environment variable BL_API_KEY, credentials
file, etc.) so that stale or mismatched credentials are immediately
obvious.

Adds:
- cli/core/auth_source.go: ResolveAuthSource, IsAuthError, PrintAuthSourceHint
- Wires auth source resolution into PersistentPreRunE
- PrintError now appends auth source hint on auth failures

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

The hint now fires from ExitWithError (the universal exit point) so it
covers error paths that use fmt.Printf directly (e.g. bl get models)
rather than PrintError. A dedup flag ensures the hint is printed at most
once even when both PrintError and ExitWithError are on the same path.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
mendral-app[bot]

This comment was marked as outdated.

Addresses review feedback: '401'/'403' substring matching could false-
positive on port numbers or resource IDs. Now matches '401 '/'403 '
(trailing space) which aligns with the SDK error format
'401 Unauthorized'. Added tests for false-positive scenarios.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@drappier-charles drappier-charles marked this pull request as ready for review June 11, 2026 05:18
@mendral-app

mendral-app Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🔍 Interaction Flow Diagram

Here's a sequence diagram showing how the new authentication source hint feature flows through the CLI components:

sequenceDiagram
    participant User
    participant RootCmd as root.go<br/>PersistentPreRunE
    participant AuthSrc as auth_source.go<br/>AuthSource module
    participant API as Blaxel API
    participant ErrHandler as sentry.go / utils.go<br/>Error Handlers

    Note over RootCmd: Initialization Phase
    User->>RootCmd: Execute CLI command
    RootCmd->>RootCmd: Create blaxel client
    RootCmd->>AuthSrc: ResolveAuthSource(workspace)
    alt Config file has credentials
        AuthSrc-->>AuthSrc: Check APIKey / AccessToken / ClientCredentials
    else Env var fallback
        AuthSrc-->>AuthSrc: Check BL_API_KEY / BL_CLIENT_CREDENTIALS
    end
    AuthSrc-->>RootCmd: AuthSource{Method, Origin}
    RootCmd->>AuthSrc: SetAuthSource(source)

    Note over API: Command Execution Phase
    RootCmd->>API: Authenticated API call
    API-->>ErrHandler: 401/403 error response

    Note over ErrHandler: Error Handling Phase
    ErrHandler->>AuthSrc: IsAuthError(err)
    AuthSrc-->>ErrHandler: true
    ErrHandler->>AuthSrc: PrintAuthSourceHint()
    AuthSrc->>AuthSrc: Check dedup flag (authHintPrinted)
    AuthSrc->>User: ⚠ "Authentication is using [METHOD] from [ORIGIN]"
    alt Credentials from env var
        AuthSrc->>User: "unset BL_API_KEY / BL_CLIENT_CREDENTIALS"
    end
Loading

Summary

The PR adds a credential-source tracking module (auth_source.go) that:

  1. Resolves where credentials come from during CLI init (config file vs env var)
  2. Stores that info as session state for later use
  3. Detects auth errors (401/403) in both PrintError() and ExitWithError() paths
  4. Prints a contextual hint (with deduplication) telling the user exactly which credential source is active — helping debug stale/mismatched credential issues

Note

Posted by PR Sequence Diagram · Tag @mendral-app with feedback.

@drappier-charles drappier-charles merged commit f19131b into main Jun 11, 2026
7 of 11 checks passed
@drappier-charles drappier-charles deleted the cdrappier/devin/auth-source-hint-on-error branch June 11, 2026 05:19

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread cli/core/auth_source.go
Comment on lines +82 to +85
return strings.Contains(msg, "401 ") ||
strings.Contains(msg, "403 ") ||
strings.Contains(msg, "unauthorized") ||
strings.Contains(msg, "permission denied")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 IsAuthError false-positive on OS-level "permission denied" filesystem errors

The IsAuthError function at cli/core/auth_source.go:85 matches strings.Contains(msg, "permission denied") to detect HTTP 403-style auth errors. However, this also matches Go's standard os.ErrPermission (whose .Error() is literally "permission denied"), causing filesystem permission errors to be misclassified as authentication failures.

When a user has credentials configured (so authSource is populated) and a filesystem operation fails with "permission denied" (e.g., can't read a config file or working directory), PrintError (cli/core/utils.go:378) and ExitWithError (cli/core/sentry.go:86) will print a misleading hint like "Authentication is using API key from environment variable BL_API_KEY" — which has nothing to do with the actual error. This could lead users to waste time investigating or rotating their API keys for what is actually a file permission problem.

Realistic code paths that could trigger this

Multiple call sites pass potentially filesystem-sourced errors to PrintError + ExitWithError:

  • cli/push.go:207: core.PrintError("Push", fmt.Errorf("failed to get current working directory: %w", err))
  • cli/push.go:301: core.PrintError("Push", fmt.Errorf("failed to read .env.build file: %w", buildEnvErr))
  • cli/serve.go:139: core.PrintError("Serve", err) after os.Getwd() failure

All of these could produce errors containing "permission denied" from the OS.

Prompt for agents
The IsAuthError function in cli/core/auth_source.go matches the substring "permission denied" to detect HTTP 403-style authorization failures. However, this also matches Go OS-level permission errors (os.ErrPermission is literally "permission denied"), causing false positives for filesystem errors.

The intended match is the auth error produced at cli/auth/utils.go:76 which returns fmt.Errorf("permission denied for workspace %q", workspace). Consider tightening the match to something more specific like "permission denied for workspace" or checking for os.ErrPermission / os.PathError with errors.Is/errors.As and excluding those before the string match. Alternatively, the auth/utils.go error message could be changed to use a more distinctive phrase like "access denied" that won't collide with POSIX permission errors.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread cli/core/auth_source.go
Comment on lines +36 to +43
// ResolveAuthSource determines which credentials the CLI is actually using.
//
// Resolution order (mirrors the SDK):
// 1. Config-file credentials for the workspace (~/.blaxel/config.yaml) override
// environment variables because NewClientFromConfig appends them after
// DefaultClientOptions.
// 2. If no config-file credentials exist for the workspace, environment
// variables BL_API_KEY / BL_CLIENT_CREDENTIALS are used.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🚩 ResolveAuthSource resolution order claims to mirror SDK — not independently verifiable

The comment at cli/core/auth_source.go:37-43 states that config-file credentials override environment variables because NewClientFromConfig appends them after DefaultClientOptions ("last wins"). This resolution order is critical for the auth hint to be accurate — if the SDK actually uses env vars over config (or vice versa), the hint would mislead users about which credentials are active. The SDK source (github.com/blaxel-ai/sdk-go v0.18.0) isn't available locally to verify. If the precedence is wrong, users would get an incorrect hint (e.g., told their API key env var is being used when it's actually the config file credential, or vice versa).

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it.

@mendral-app mendral-app Bot 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.

LGTM

Previous feedback was addressed: IsAuthError now uses trailing-space matching ("401 " / "403 ") and includes tests for false-positive scenarios. The implementation is clean and well-structured for a CLI diagnostic feature.

Tag @mendral-app with feedback or questions. View session

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.

1 participant