Skip to content

feat(github): add credential_command setting for custom token retrieval#8746

Merged
jdx merged 6 commits intomainfrom
feat/github-credential-command
Mar 24, 2026
Merged

feat(github): add credential_command setting for custom token retrieval#8746
jdx merged 6 commits intomainfrom
feat/github-credential-command

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 24, 2026

Summary

  • Adds a new github.credential_command setting — a shell command mise executes via sh -c to obtain a GitHub token from stdout
  • When set, replaces the git credential fill fallback (priority 5), allowing a mise-only credential source that doesn't affect git config
  • Does not require use_git_credentials to be enabled
  • Results are cached per session

Example:

[settings.github]
credential_command = "op read 'op://Private/GitHub Token/credential'"

Test plan

  • Unit tests pass (cargo test -- github::tests, test_settings_toml_is_sorted)
  • E2E tests pass (mise run test:e2e test_github_token) — added tests for credential_command, priority over git credential fill, and env var precedence
  • Manual verification: MISE_GITHUB_CREDENTIAL_COMMAND='echo ghp_test' mise github token --unmask
  • CI passes

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it changes GitHub token resolution behavior and introduces execution of user-provided shell commands, which can affect auth/header generation across GitHub and GHE requests.

Overview
Adds a new github.credential_command setting (and MISE_GITHUB_CREDENTIAL_COMMAND env var) that runs a host-aware sh -c command to return a GitHub token, caching results per host for the session.

Updates token resolution priority so credential_command replaces the git credential fill fallback when set, and surfaces the new source via TokenSource for mise github token debugging.

Extends schema/docs and e2e coverage to document the new option, validate precedence (env vars still win; credential_command beats git credentials), and verify host argument passing.

Written by Cursor Bugbot for commit cecf0e4. This will update automatically on new commits. Configure here.

…eval

Adds a new `github.credential_command` setting that lets users configure
a shell command mise executes to obtain a GitHub token. The command runs
via `sh -c` and mise reads the token from stdout.

When set, this replaces the `git credential fill` fallback at priority 5,
allowing users to define a credential source that only mise uses without
affecting git configuration. Does not require `use_git_credentials`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances mise's GitHub token retrieval capabilities by introducing a new credential_command setting. This feature allows users to define a custom shell command that mise will execute to obtain a GitHub token, providing a flexible and isolated method for integrating with various secret management systems or custom scripts. It streamlines the process of securing and accessing GitHub tokens within mise without relying on or interfering with Git's credential helpers.

Highlights

  • New credential_command setting: Introduced github.credential_command which allows mise to execute a shell command to obtain a GitHub token from stdout.
  • Credential Priority: When credential_command is set, it replaces the git credential fill fallback, providing a mise-only credential source without affecting Git configuration.
  • Independent Credential Source: This new setting does not require use_git_credentials to be enabled, offering greater flexibility.
  • Session Caching: Results from the credential_command are cached per session to optimize performance.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR introduces github.credential_command (and MISE_GITHUB_CREDENTIAL_COMMAND) — a new last-resort token source that runs an arbitrary shell command via sh -c, passing the hostname as $1, and reads the token from stdout. It slots in at priority 5 (replacing git credential fill when set), caches results per-host in a session-scoped HashMap, captures stderr at debug! level, and surfaces the source in mise github token output via a new TokenSource::CredentialCommand variant.

  • Bug: is_gh_host not updatedhttp.rs uses is_gh_host to gate whether auth headers are sent to GHE hosts. The function only checks github_tokens.toml and the gh CLI; it does not account for credential_command. Users who configure credential_command as their only GHE credential source will see mise github token ghe.corp.com succeed while actual HTTP requests to that host go unauthenticated.
  • The per-host HashMap cache correctly addresses the previous comment about a single-slot cache not being keyed by host, and stderr capture at debug! level addresses the previous comment about silent failures.
  • Caching None on failure is a standard tradeoff but worth documenting in the function's doc-comment so users know they must restart mise if their credential provider needs to be unlocked after startup.
  • E2e tests cover token output, source label, priority order, env-var precedence, and host-argument passing — but not the GHE HTTP request code path where the is_gh_host gap would manifest.

Confidence Score: 4/5

  • Nearly safe to merge — one concrete bug in is_gh_host will silently drop auth headers for GHE users who rely solely on credential_command.
  • The core implementation is solid: per-host caching, stderr capture, correct priority ordering, and good e2e coverage. The previous concerns about silent fallback blocking and cache keying have been addressed. The one remaining concrete bug — is_gh_host not accounting for credential_command — will cause unauthenticated GHE HTTP requests for users who use this feature as their only GHE credential source, which is a plausible and documented use case. A one-line fix unblocks merge.
  • src/github.rs — specifically the is_gh_host function (line 432) needs to return true when credential_command is non-empty to ensure auth headers are attached to GHE HTTP requests.

Important Files Changed

Filename Overview
src/github.rs Core implementation of credential_command: adds get_credential_command_token with per-host caching, stderr capture at debug level, and host-as-$1 passing. is_gh_host is not updated to account for the new source, which will silently drop auth headers on GHE HTTP requests when credential_command is the only credential configured.
e2e/cli/test_github_token Good e2e coverage: tests token output, source label, priority over git-credential-fill, env-var precedence, and host-argument passing. Does not test the GHE HTTP request path (which is where the is_gh_host bug would manifest).
settings.toml Adds github.credential_command setting entry with env var, default, type, and docs. Looks correct and consistent with surrounding settings.
docs/dev-tools/github-tokens.md Documentation is clear and accurate: explains sh -c execution, host-as-$1 semantics, session caching, and priority placement. The GHE section correctly mentions credential_command as an option for multiple GHE instances.
schema/mise.json Schema updated to add credential_command property to the github object with correct type, default, and description.

Sequence Diagram

sequenceDiagram
    participant User
    participant http_rs as http.rs (github_headers)
    participant is_gh_host as github::is_gh_host
    participant resolve_token as github::resolve_token
    participant cmd_cache as CREDENTIAL_COMMAND_CACHE
    participant sh as sh -c (credential_command)

    User->>http_rs: HTTP request to ghe.corp.com
    http_rs->>is_gh_host: is_gh_host("ghe.corp.com")
    Note over is_gh_host: Checks MISE_GITHUB_TOKENS<br/>and GH_HOSTS only<br/>(credential_command NOT checked ⚠️)
    is_gh_host-->>http_rs: false (if only credential_command is set)
    http_rs-->>User: Request sent WITHOUT auth headers ⚠️

    User->>resolve_token: mise github token ghe.corp.com
    resolve_token->>cmd_cache: cache.get("ghe.corp.com")
    alt Cache miss
        cmd_cache-->>resolve_token: None
        resolve_token->>sh: sh -c "op read ..." mise-credential-helper ghe.corp.com
        sh-->>resolve_token: token on stdout
        resolve_token->>cmd_cache: cache.insert("ghe.corp.com", Some(token))
        resolve_token-->>User: (token, CredentialCommand)
    else Cache hit
        cmd_cache-->>resolve_token: cached token (or None)
        resolve_token-->>User: cached result
    end
Loading

Fix All in Claude Code

Reviews (5): Last reviewed commit: "[autofix.ci] apply automated fixes (atte..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new credential_command feature for GitHub token retrieval, allowing users to define a custom shell command to fetch tokens. This new method is integrated into the token resolution priority, documented, added to the settings schema, and includes new end-to-end tests. A review comment suggests an improvement to make the credential_command host-aware, similar to other token sources, to enhance its utility in multi-host environments.

Comment on lines +547 to +585
/// Cache for tokens obtained from `credential_command`.
static CREDENTIAL_COMMAND_CACHE: Lazy<std::sync::Mutex<Option<Option<String>>>> =
Lazy::new(Default::default);

/// Get a GitHub token by running the user's `credential_command` setting.
/// The result is cached so the command is only spawned once per session.
fn get_credential_command_token(cmd: &str) -> Option<String> {
let mut cache = CREDENTIAL_COMMAND_CACHE
.lock()
.expect("CREDENTIAL_COMMAND_CACHE mutex poisoned");
if let Some(token) = cache.as_ref() {
return token.clone();
}
let result = std::process::Command::new("sh")
.args(["-c", cmd])
.env("GIT_TERMINAL_PROMPT", "0")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| {
String::from_utf8(output.stdout)
.ok()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
});
trace!(
"credential_command: {}",
if result.is_some() {
"found"
} else {
"not found"
}
);
*cache = Some(result.clone());
result
}
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.

medium

The credential_command feature is a great addition for flexible token retrieval. However, the current implementation is not host-aware, meaning it will return the same token for github.com and any GitHub Enterprise instances. This limits its usefulness in environments with multiple GitHub hosts, which is a scenario the documentation suggests is supported.

To make this feature more powerful and consistent with other token sources like git credential fill and gh CLI, I suggest making it host-aware. The host could be passed as an argument to the command, making it available to the user's script (e.g., as $1).

This would involve:

  1. Changing get_credential_command_token to accept a host argument and updating the call in resolve_token.
  2. Updating the cache CREDENTIAL_COMMAND_CACHE to be a HashMap keyed by host, similar to GIT_CREDENTIAL_CACHE.
  3. Passing the host to the shell command. A safe way to do this is by passing it as a positional parameter.

Here's a suggested implementation (don't forget to update the call in resolve_token):

/// Cache for tokens obtained from `credential_command`.
static CREDENTIAL_COMMAND_CACHE: Lazy<std::sync::Mutex<HashMap<String, Option<String>>>> =
    Lazy::new(Default::default);

/// Get a GitHub token by running the user's `credential_command` setting.
/// The result is cached so the command is only spawned once per session per host.
fn get_credential_command_token(cmd: &str, host: &str) -> Option<String> {
    let mut cache = CREDENTIAL_COMMAND_CACHE
        .lock()
        .expect("CREDENTIAL_COMMAND_CACHE mutex poisoned");
    if let Some(token) = cache.get(host) {
        return token.clone();
    }
    let result = std::process::Command::new("sh")
        .arg("-c")
        .arg(cmd)
        .arg("mise-credential-helper") // $0
        .arg(host) // $1
        .env("GIT_TERMINAL_PROMPT", "0")
        .stdin(std::process::Stdio::null())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::null())
        .output()
        .ok()
        .filter(|output| output.status.success())
        .and_then(|output| {
            String::from_utf8(output.stdout)
                .ok()
                .map(|s| s.trim().to_string())
                .filter(|s| !s.is_empty())
        });
    trace!(
        "credential_command for {host}: {}",
        if result.is_some() {
            "found"
        } else {
            "not found"
        }
    );
    cache.insert(host.to_string(), result.clone());
    result
}

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 24, 2026

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.14 x -- echo 27.2 ± 0.9 26.3 43.3 1.00
mise x -- echo 27.3 ± 0.5 26.4 31.0 1.00 ± 0.04

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.14 env 26.4 ± 0.7 25.7 34.2 1.01 ± 0.03
mise env 26.2 ± 0.3 25.6 27.3 1.00

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.14 hook-env 27.1 ± 0.3 26.4 28.7 1.00 ± 0.01
mise hook-env 27.0 ± 0.3 26.5 27.9 1.00

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.3.14 ls 26.4 ± 0.4 25.7 27.8 1.00
mise ls 26.7 ± 0.4 25.7 28.2 1.01 ± 0.02

xtasks/test/perf

Command mise-2026.3.14 mise Variance
install (cached) 174ms 173ms +0%
ls (cached) 92ms 91ms +1%
bin-paths (cached) 94ms 94ms +0%
task-ls (cached) 837ms 829ms +0%

jdx and others added 3 commits March 24, 2026 17:13
- Pass hostname as $1 to credential_command for GHE support
- Cache results per host (HashMap) instead of single slot
- Capture stderr at debug! level instead of discarding it
- Log when credential_command returns no token (debug level)
- Update docs and settings to document $1 host argument

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 131ed57 into main Mar 24, 2026
35 checks passed
@jdx jdx deleted the feat/github-credential-command branch March 24, 2026 22:36
mise-en-dev added a commit that referenced this pull request Mar 25, 2026
### 🚀 Features

- **(github)** add `credential_command` setting for custom token
retrieval by @jdx in [#8746](#8746)

### 🐛 Bug Fixes

- **(github)** raise credential_command priority above
github_tokens.toml and gh CLI by @jdx in
[#8748](#8748)
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