gastownhall/beads · error
oauth: token request failed (%s): %s
Error message
oauth: token request failed (%s): %s
What it means
The token endpoint returned a non-200 status and its JSON body contained an OAuth error field (RFC 6749 error responses such as invalid_client or invalid_grant). The server explicitly rejected the token request.
Source
Thrown at internal/linear/oauth.go:143
return fmt.Errorf("oauth: failed to create token request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := m.client.Do(req)
if err != nil {
return fmt.Errorf("oauth: token request failed: %w", err)
}
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) // 1MB limit
if err != nil {
return fmt.Errorf("oauth: failed to read token response: %w", err)
}
if resp.StatusCode != http.StatusOK {
var errResp oauthErrorResponse
if json.Unmarshal(body, &errResp) == nil && errResp.Error != "" {
return fmt.Errorf("oauth: token request failed (%s): %s", errResp.Error, errResp.Description)
}
return fmt.Errorf("oauth: token request returned status %d: %s", resp.StatusCode, string(body))
}
var tokenResp oauthTokenResponse
if err := json.Unmarshal(body, &tokenResp); err != nil {
return fmt.Errorf("oauth: failed to parse token response: %w", err)
}
if tokenResp.AccessToken == "" {
return fmt.Errorf("oauth: token response missing access_token")
}
m.token = tokenResp.AccessToken
m.expiresAt = m.nowFunc().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)
debug.Logf("oauth: acquired token (expires in %ds)", tokenResp.ExpiresIn)
return nilView on GitHub (pinned to 71377f2769)
Solutions
- Read errResp.Error/Description in the wrapped message and fix the matching OAuth parameter.
- Re-verify client_id and client_secret against the provider (most common: invalid_client after rotation).
- Trim the requested scopes to those actually granted to the integration.
- If credentials live in env/secrets, confirm the deployment is using the refreshed values.
Example fix
// before
"client_secret": {m.config.ClientSecret} // stale after rotation
// after
"client_secret": {secretFromSecretManager}" // refreshed credential
Defensive patterns
Strategy: validation
Validate before calling
if cfg.ClientID == "" || cfg.ClientSecret == "" {
return fmt.Errorf("oauth client credentials missing")
} Try / catch
var oauthErr *OAuthError // parse errResp.Error/Description from message
if strings.Contains(err.Error(), "invalid_client") {
// rotate credentials and reload config
} Prevention
- Rotate and verify client credentials before deployments.
- Keep requested scopes minimal and matching the provider's granted scopes.
- Alert on invalid_client/invalid_grant — they mean config drift, not transient faults.
When it happens
Trigger: resp.StatusCode != 200 and json.Unmarshal into oauthErrorResponse succeeds with a non-empty Error field during acquireToken.
Common situations: Wrong client_id/client_secret after a credential rotation; invalid scopes requested; expired or revoked authorization; grant_type mismatch with what the server supports.
Related errors
- failed to fetch teams: %w
- oauth: failed to create token request: %w
- oauth: token request failed: %w
- oauth: failed to read token response: %w
- oauth: token request returned status %d: %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3b513b24a5239e9e.
Report an issue: GitHub.