gastownhall/beads · error
credential command failed: %w
Error message
credential command failed: %w
What it means
resolveCredentialToken runs the configured credential command under a timeout via credRunner. If the command itself fails (non-zero exit, not runnable, timeout), the failure is wrapped with the 'credential command failed:' prefix so the caller knows which stage of token resolution broke.
Source
Thrown at internal/creds/command.go:123
// resolveCredentialToken returns the token (and any username/expiry) for the given
// helper command, using a process-level cache keyed by the command so repeated opens
// don't re-spawn the helper until the token is near expiry. It is concurrency-safe.
func resolveCredentialToken(ctx context.Context, command string) (token, username string, expiry time.Time, err error) {
now := time.Now()
credCacheMu.Lock()
if c, ok := credCache[command]; ok && now.Before(c.expires.Add(-credExpirySkew)) {
tok, user, exp := c.token, c.username, c.expires
credCacheMu.Unlock()
return tok, user, exp, nil
}
credCacheMu.Unlock()
runCtx, cancel := context.WithTimeout(ctx, credCommandTimeout)
defer cancel()
raw, err := credRunner(runCtx, command)
if err != nil {
return "", "", time.Time{}, fmt.Errorf("credential command failed: %w", err)
}
token, username, expiry, err = parseCredential(raw)
if err != nil {
return "", "", time.Time{}, err
}
if expiry.IsZero() {
expiry = now.Add(credDefaultTTL)
}
credCacheMu.Lock()
credCache[command] = cachedCred{token: token, username: username, expires: expiry}
credCacheMu.Unlock()
return token, username, expiry, nil
}
// parseCredential extracts the token (and any username/expiry) from a helper's
// stdout. A JSON object is read as the ExecCredential/getToken envelope; otherwise
// the trimmed output is taken as a bare token. A bare value containing whitespace isView on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped cause: 'executable file not found' means fix PATH or the configured command; 'exit status N' means debug the helper's stderr; 'context deadline exceeded' means the helper is too slow.
- Install or reinstall the credential helper binary and confirm it is on PATH.
- Run the credential command manually to verify it outputs a token.
- If the helper is slow, check network connectivity to the auth provider (e.g. `gh auth status`).
Example fix
// before credentialCommand = "gh-helper-missing" // after credentialCommand = "gh" // verified: gh auth token works on this machine
Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := exec.LookPath(strings.Fields(command)[0]); err != nil {
// command not found; repair before calling Resolve
} Try / catch
token, _, _, err := creds.Resolve(ctx)
if err != nil && strings.Contains(err.Error(), "credential command failed") {
// fall back to another source or prompt the user
return fmt.Errorf("resolve token: %w", err)
} Prevention
- Verify the configured credential command exists on PATH on every machine bd runs on.
- Keep helper runtime well under the command timeout (avoid network-heavy helpers or pre-warm auth).
- Handle 'context deadline exceeded' by checking helper latency.
When it happens
Trigger: The credential command is not found on PATH (exec.ErrNotFound), exits non-zero, or exceeds credCommandTimeout and is killed via the runCtx context deadline.
Common situations: Misconfigured credential command string in config; helper binary uninstalled or renamed after a version change; slow helper (network auth) hitting the command timeout; helper crashing on startup.
Related errors
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e86729ca48f991bc.
Report an issue: GitHub.