Skip to content

fix(github): clearer error when a GitHub token is rejected with 401#11236

Merged
jdx merged 3 commits into
jdx:mainfrom
Marukome0743:pr11235
Jul 26, 2026
Merged

fix(github): clearer error when a GitHub token is rejected with 401#11236
jdx merged 3 commits into
jdx:mainfrom
Marukome0743:pr11235

Conversation

@Marukome0743

@Marukome0743 Marukome0743 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Problem

When GitHub rejects an invalid, expired, wrong-host, or insufficiently scoped
token, mise previously returned only a bare 401 Unauthorized. The initial
change added an actionable report, but only environment-variable tokens could
be attributed precisely; tokens from the gh CLI, github_tokens.toml, OAuth,
credential commands, and Git credentials were reported generically.

Reported in discussion #7218.

Change

When a GitHub API request returns 401, mise now:

  • reports whether an Authorization header was sent and names every known
    TokenSource;
  • includes a bounded GitHub response body and a source-specific remediation
    hint;
  • records the source when building the header, then matches the exact host and
    Bearer token on the error path instead of resolving credentials again;
  • preserves the existing OAuth refresh-first behavior and records refreshed
    tokens as GitHub OAuth;
  • documents the 401 diagnostic next to the existing 403 guidance in
    docs/errors.md.

Example:

HTTP status client error (401 Unauthorized) for url (https://api.github.com/…)
github auth: yes (token from gh CLI (hosts.yml))
github response: Bad credentials
hint: the token from gh CLI (hosts.yml) was rejected by GitHub (401 Unauthorized).

The host-and-token match prevents netrc or caller-provided Authorization headers
from being attributed to an unrelated mise credential. It also avoids calling
resolve_token from the async error path, where OAuth resolution could perform
a blocking refresh.

Out of scope

Automatically retrying unauthenticated on 401 is intentionally not done; that
behavior was rejected in #9965. The existing bounded error-body handling and
OAuth retry remain unchanged.

Testing

  • cargo fmt --all -- --check
  • markdownlint docs/errors.md
  • cargo check --all-features
  • GitHub header-source tests for environment variables, github_tokens.toml,
    host mismatches, and token mismatches
  • 401 report tests for all TokenSource variants, unknown sources, and
    unauthenticated requests
  • OAuth tests for successful refresh/retry and source attribution when the
    refreshed token is also rejected

Fixes discussion #7218.


This pull request was generated by an AI coding assistant.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds shared GitHub token environment-variable selection and detailed diagnostics for GitHub API 401 Unauthorized responses, including bounded response bodies, token-source hints, and authentication-source tests.

Changes

GitHub authentication diagnostics

Layer / File(s) Summary
Token environment selection
src/github.rs
Centralizes token environment-variable precedence, adds host-based token-source lookup, reuses the shared list during token resolution, and tests token attribution.
Unauthorized response reporting
src/http.rs
Intercepts GitHub API 401 responses, bounds GitHub error-body reads, reports authentication details and token hints, updates 403 handling, and tests known, unknown, unused-token, and truncation cases.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant GitHubAPI
  participant send_once_inner
  participant github_unauthorized_report
  Client->>GitHubAPI: Send authenticated request
  GitHubAPI-->>send_once_inner: Return 401 Unauthorized
  send_once_inner->>github_unauthorized_report: Pass auth presence, token source, and bounded response body
  github_unauthorized_report-->>Client: Return structured diagnostic report
Loading

Poem

A rabbit found a token trail,
Through GitHub’s fields and headers pale.
A 401 came hopping by,
With bounded clues instead of a sigh.
“Check your env!” the bunny cried.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: improved GitHub 401 handling for rejected tokens.

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.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds clearer diagnostics for GitHub 401 responses.

  • Records the source of credentials used for GitHub requests.
  • Reports authentication state, a bounded response body, and source-specific remediation guidance.
  • Preserves OAuth refresh behavior and documents the new diagnostic.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failures remain within the scope of the follow-up review.

Important Files Changed

Filename Overview
src/github.rs Adds token-source tracking by host and exact Bearer token, with coverage for environment and token-file credentials.
src/http.rs Adds bounded GitHub 401 reporting, records refreshed OAuth credentials, and applies bounded body reading to GitHub 403 diagnostics.
docs/errors.md Documents the authentication details and remediation guidance emitted for GitHub 401 responses.

Reviews (7): Last reviewed commit: "test(github): isolate token source tests" | Re-trigger Greptile

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/http.rs`:
- Around line 880-884: Update the auth status match in the request handling flow
to only report the env-var token label when used_github_token is true;
unauthenticated requests must remain “no” even if env_var is present. Add a
regression case covering false with Some("GITHUB_TOKEN").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: acfaa51b-d00b-48bf-8c66-d9f6edc68066

📥 Commits

Reviewing files that changed from the base of the PR and between ce3972c and 5642be6.

📒 Files selected for processing (2)
  • src/github.rs
  • src/http.rs

Comment thread src/http.rs Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/http.rs`:
- Line 764: Update the 401 response handling around format_response_body so the
response body is streamed with a fixed byte budget before formatting, rather
than fully buffered by resp.text(). Reuse the same bounded-body helper for the
equivalent 403 handling, preserving the existing formatted error behavior while
ensuring oversized or indefinitely trickling responses cannot consume unbounded
memory or block indefinitely.
- Around line 760-764: Update the response-error handling around
used_github_token, env_var, and resp.text() so env_var is retained only when the
sent Authorization Bearer credential matches the selected environment token;
caller-provided or netrc headers must use the generic authenticated wording. Add
a regression case covering a netrc or custom Authorization header alongside an
unrelated environment token.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e26a875-bcd2-4177-be18-74d8a77925c0

📥 Commits

Reviewing files that changed from the base of the PR and between 5642be6 and 1ee2574.

📒 Files selected for processing (2)
  • src/github.rs
  • src/http.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/github.rs

Comment thread src/http.rs Outdated
Comment thread src/http.rs Outdated
@Marukome0743
Marukome0743 force-pushed the pr11235 branch 2 times, most recently from a1671db to 4bfb7f6 Compare July 24, 2026 06:04

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/github.rs`:
- Around line 624-633: Update env_var_for_token so standard-token matching
ignores environment variables whose trimmed value is empty, preserving the
filtering behavior used by resolve_token. Ensure token == "" cannot match any
GITHUB_TOKEN_ENV_VARS entry, while retaining existing matching for non-empty
trimmed tokens and enterprise-token handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 86c2e4e4-874b-44ab-a261-72a9033391d2

📥 Commits

Reviewing files that changed from the base of the PR and between a1671db and 4bfb7f6.

📒 Files selected for processing (2)
  • src/github.rs
  • src/http.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/http.rs

Comment thread src/github.rs Outdated
mise already surfaces a helpful message for GitHub 403 responses (auth state,
rate limit, response body), but a 401 from a static invalid/expired token — the
common case being an invalid or enterprise `GITHUB_TOKEN` that isn't valid for
github.com — fell straight through to a bare `HTTP status client error (401
Unauthorized)` with no hint that mise was authenticating or which env var
supplied the token.

Add a 401 branch mirroring the existing 403 handling: when a GitHub API request
returns 401, return an error that reports the auth state, names the token env
var when one is set, includes the response body, and links to the token guide.

The token env var is looked up via a new side-effect-free
`github::auth_env_var_for_host` (no credential_command / oauth / network), so the
error path can't re-spawn the credential helper. The existing OAuth device-flow
401-refresh path is unchanged and still runs first for mise-managed tokens.

This intentionally does not add an automatic unauthenticated retry on 401, which
was rejected in jdx#9965.

Fixes discussion jdx#7218.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Marukome0743
Marukome0743 marked this pull request as ready for review July 24, 2026 21:10
@jdx

jdx commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Direction here is right — this is the "at minimum, tell me which env var supplied the token" ask from #7218, without re-litigating the unauthenticated retry from #9965. Scope is fine by me, including the bounded error-body read.

Two things I'd like:

  1. Attribution should cover the non-env-var sources. get_headers already resolves the token and its TokenSource, then drops it on the floor:

    if let Some((token, _source)) = resolve_token(url.host_str().unwrap_or("github.com"))

    If that source is captured where the header is built (per-host memo, or carried alongside the retry state), the 401 report can name a stale gh auth login token, a github_tokens.toml entry, or an expired OAuth token — TokenSource's Display impl already renders exactly the strings you'd want (gh CLI (hosts.yml), github_tokens.toml, ...). Right now those all land on the vaguer "configured GitHub token". Same message shape, wider coverage, and no env-var list to keep in sync.

    For what it's worth, the reasoning in the doc comment on env_var_for_token is right but for the wrong reason: credential_command and git credential fill are both memoized per provider:host, so re-resolving wouldn't re-prompt those. The real hazard is oauth::resolve_tokentoken()block_on(token_async(..)), i.e. a blocking refresh from inside the async send path. Worth fixing the comment either way.

  2. Add a docs/errors.md entry. The 403 message is documented there and calls out the github auth: line; 401 should sit right next to it.

Neither is a correctness problem — as it stands this is a clear improvement on a bare 401 Unauthorized. Happy to take (1) in this PR if you want to keep it together, or as a follow-up.

This comment was generated by an AI coding assistant.

@Marukome0743

Copy link
Copy Markdown
Contributor Author

Addressed both follow-ups in b3ac992:

  • get_headers now records the resolved TokenSource with the exact host/token used to build the Authorization header. The 401 path only reports that source when the sent Bearer token matches, so netrc or caller-provided headers are not misattributed and no credential resolution runs from the async error path.
  • Refreshed OAuth tokens are recorded as GitHub OAuth, and the report now covers every existing TokenSource display value.
  • Added the adjacent 401 guidance to docs/errors.md.

Validated with the focused header-source, 401-report, and OAuth refresh tests, plus cargo fmt --all -- --check, markdownlint docs/errors.md, and cargo check --all-features.

This comment was generated by an AI coding assistant.

@jdx
jdx merged commit a35eebf into jdx:main Jul 26, 2026
34 checks passed
@Marukome0743
Marukome0743 deleted the pr11235 branch July 26, 2026 00:12
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.

2 participants