feat(github): add credential_command setting for custom token retrieval#8746
feat(github): add credential_command setting for custom token retrieval#8746
credential_command setting for custom token retrieval#8746Conversation
…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>
Summary of ChangesHello, 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 Highlights
Using Gemini Code AssistThe 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
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 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
|
Greptile SummaryThis PR introduces
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
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
Reviews (5): Last reviewed commit: "[autofix.ci] apply automated fixes (atte..." | Re-trigger Greptile |
There was a problem hiding this comment.
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.
| /// 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 | ||
| } |
There was a problem hiding this comment.
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:
- Changing
get_credential_command_tokento accept ahostargument and updating the call inresolve_token. - Updating the cache
CREDENTIAL_COMMAND_CACHEto be aHashMapkeyed by host, similar toGIT_CREDENTIAL_CACHE. - 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
}
Hyperfine Performance
|
| 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% |
- 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>
Summary
github.credential_commandsetting — a shell command mise executes viash -cto obtain a GitHub token from stdoutgit credential fillfallback (priority 5), allowing a mise-only credential source that doesn't affect git configuse_git_credentialsto be enabledExample:
Test plan
cargo test -- github::tests,test_settings_toml_is_sorted)mise run test:e2e test_github_token) — added tests for credential_command, priority over git credential fill, and env var precedenceMISE_GITHUB_CREDENTIAL_COMMAND='echo ghp_test' mise github token --unmask🤖 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_commandsetting (andMISE_GITHUB_CREDENTIAL_COMMANDenv var) that runs a host-awaresh -ccommand to return a GitHub token, caching results per host for the session.Updates token resolution priority so
credential_commandreplaces thegit credential fillfallback when set, and surfaces the new source viaTokenSourceformise github tokendebugging.Extends schema/docs and e2e coverage to document the new option, validate precedence (env vars still win;
credential_commandbeats git credentials), and verify host argument passing.Written by Cursor Bugbot for commit cecf0e4. This will update automatically on new commits. Configure here.