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 is

View on GitHub (pinned to 71377f2769)

Solutions

  1. 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.
  2. Install or reinstall the credential helper binary and confirm it is on PATH.
  3. Run the credential command manually to verify it outputs a token.
  4. 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

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.