hashicorp/packer · error

request GitHub OIDC token: unexpected status %s

Error message

request GitHub OIDC token: unexpected status %s

What it means

GitHub's OIDC token endpoint responded, but with a non-2xx status code. The full HTTP status string (e.g. "403 Forbidden", "401 Unauthorized") is embedded in the error. This means the audience-augmented token request was rejected by the server, typically due to authentication or permissions.

Source

Thrown at internal/attestation/sign_keyless.go:350

		query.Set("audience", "sigstore")
		parsedURL.RawQuery = query.Encode()
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsedURL.String(), nil)
	if err != nil {
		return "", fmt.Errorf("create GitHub OIDC request: %w", err)
	}
	req.Header.Set("Authorization", "Bearer "+requestToken)

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("request GitHub OIDC token: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode/100 != 2 {
		return "", fmt.Errorf("request GitHub OIDC token: unexpected status %s", resp.Status)
	}

	var payload struct {
		Value string `json:"value"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
		return "", fmt.Errorf("decode GitHub OIDC token response: %w", err)
	}
	return strings.TrimSpace(payload.Value), nil
}

View on GitHub (pinned to eb36e3c3e4)

Solutions

  1. Read the embedded status: 403/404 usually means missing `permissions: id-token: write`; 401 means the request token is invalid or expired.
  2. Add `permissions: id-token: write` to the workflow/job in GitHub Actions.
  3. Ensure the token request happens within the same job run that issued ACTIONS_ID_TOKEN_REQUEST_TOKEN.
  4. Confirm the repository/organization OIDC policy allows token issuance.
  5. Bypass the endpoint by supplying SIGSTORE_ID_TOKEN directly.

Example fix

// before (workflow)
jobs:
  sign:
    steps:
      - uses: actions/checkout@v4
// after
jobs:
  sign:
    permissions:
      id-token: write # also add contents: read if needed
      contents: read
    steps:
      - uses: actions/checkout@v4
Defensive patterns

Strategy: type-guard

Validate before calling

// inspect the status embedded in the error and branch
switch {
case strings.Contains(err.Error(), "403"), strings.Contains(err.Error(), "404"):
    return fmt.Errorf("workflow lacks id-token: write permission")
case strings.Contains(err.Error(), "401"):
    return fmt.Errorf("OIDC request token invalid or expired")
}

Type guard

func isOIDCPermissionError(err error) bool {
    return err != nil && (strings.Contains(err.Error(), "403") || strings.Contains(err.Error(), "404")) &&
        strings.Contains(err.Error(), "unexpected status")
}
func isOIDCAuthError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "401") && strings.Contains(err.Error(), "unexpected status")
}

Try / catch

signer, err := newKeylessSigner(ctx, cfg)
if err != nil && strings.Contains(err.Error(), "unexpected status") {
    if isOIDCPermissionError(err) {
        return fmt.Errorf("add `permissions: id-token: write` to the workflow")
    }
    return fmt.Errorf("GitHub OIDC endpoint rejected the token request: %w", err)
}

Prevention

When it happens

Trigger: resolveGitHubActionerIDToken receives a response whose StatusCode/100 != 2: the ACTIONS_ID_TOKEN_REQUEST_TOKEN is expired/invalid, the workflow lacks id-token: write permission, the repository/organization restricts OIDC, or the custom endpoint (proxy/mock) returns an error status.

Common situations: GitHub Actions job without `permissions: id-token: write` (403/404); reusing an OIDC request token from a different job after it expired (401); enterprise policy disabling OIDC for the repo; pointing ACTIONS_ID_TOKEN_REQUEST_URL at a stub server returning 500.

Related errors


AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05). Data as JSON: /api/errors/82021c528d22c31f. Report an issue: GitHub.