gastownhall/beads · error

credential command JSON has no token/access_token field

Error message

credential command JSON has no token/access_token field

What it means

If the credential command's output parses as JSON but contains neither a 'token' nor an 'access_token' field, parseCredential rejects it. The JSON envelope contract requires at least one of these fields to carry the credential.

Source

Thrown at internal/creds/command.go:160

// rejected — it is almost always an error message, and using it as a credential
// would only fail confusingly downstream.
func parseCredential(raw []byte) (token, username string, expiry time.Time, err error) {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 {
		return "", "", time.Time{}, fmt.Errorf("credential command produced no output")
	}

	if trimmed[0] == '{' {
		var c execCredential
		if jerr := json.Unmarshal(trimmed, &c); jerr != nil {
			return "", "", time.Time{}, fmt.Errorf("credential command returned unparseable JSON: %w", jerr)
		}
		token = c.Token
		if token == "" {
			token = c.AccessToken
		}
		if token == "" {
			return "", "", time.Time{}, fmt.Errorf("credential command JSON has no token/access_token field")
		}
		switch {
		case c.ExpirationTimestamp != "":
			if t, perr := time.Parse(time.RFC3339, c.ExpirationTimestamp); perr == nil {
				expiry = t
			}
		case c.ExpiresIn > 0:
			expiry = time.Now().Add(time.Duration(c.ExpiresIn) * time.Second)
		}
		return token, c.Username, expiry, nil
	}

	bare := string(trimmed)
	if strings.ContainsAny(bare, " \t\r\n") {
		return "", "", time.Time{}, fmt.Errorf("credential command output is not a bare token (contains whitespace); expected a token or a JSON {token,expirationTimestamp} envelope")
	}
	return bare, "", time.Time{}, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Update the credential helper to include a "token" or "access_token" field in its JSON output.
  2. If the helper outputs the token under another key, add a mapping/shim script that renames it to "token".
  3. Check the helper's documentation/version for its current output schema.
  4. As a workaround, have the helper print a bare token instead of JSON.

Example fix

// before
{"accessTok":"ghp_abc"}
// after
{"access_token":"ghp_abc"}
Defensive patterns

Strategy: type-guard

Validate before calling

var probe struct {
    Token       string `json:"token"`
    AccessToken string `json:"access_token"`
}
if err := json.Unmarshal(raw, &probe); err == nil && probe.Token == "" && probe.AccessToken == "" {
    // JSON lacks a token field; fix helper schema first
}

Type guard

func hasCredentialToken(raw []byte) bool {
    var c struct {
        Token       string `json:"token"`
        AccessToken string `json:"access_token"`
    }
    return json.Unmarshal(raw, &c) == nil && (c.Token != "" || c.AccessToken != "")
}

Prevention

When it happens

Trigger: Helper emits a JSON object with different field names (e.g. 'secret', 'password', 'key') or an object with only expirationTimestamp/username and no token.

Common situations: Custom credential scripts written against a different helper schema (e.g. docker's credsStore format with ServerURL/Username/Secret fields); version change where the helper renamed its output field.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/d45ba43df651efa3. Report an issue: GitHub.