plandex-ai/plandex · error
token exchange failed - error creating request: %s
Error message
token exchange failed - error creating request: %s
What it means
In the Claude Max OAuth flow, exchangeCode() performs the authorization-code-to-token exchange against claudeMaxTokenUrl. This error is returned when http.NewRequest fails to even construct the POST request — before any network I/O happens. It wraps the http.NewRequest error.
Source
Thrown at app/cli/lib/claude_max.go:242
},
}
if err := SetAccountCredentials(&creds); err != nil {
term.OutputErrorAndExit("Error setting account credentials: %v", err)
}
}
func exchangeCode(code, verifier, state string) (*types.OauthResponse, error) {
body, _ := json.Marshal(map[string]any{
"grant_type": "authorization_code",
"code": code,
"state": state,
"code_verifier": verifier,
"redirect_uri": claudeMaxRedirect,
"client_id": claudeMaxClientId,
})
req, err := http.NewRequest("POST", claudeMaxTokenUrl, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("token exchange failed - error creating request: %s", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("anthropic-beta", shared.AnthropicClaudeMaxBetaHeader)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("token exchange failed - error reading body: %s", err)
}
return nil, fmt.Errorf("token exchange failed - status: %d, body: %s", resp.StatusCode, b)
}
var t types.OauthResponseView on GitHub (pinned to e2d772072e)
Solutions
- Inspect the wrapped error; it names the URL parse failure.
- Verify claudeMaxTokenUrl is a valid absolute https URL (non-empty, with scheme).
- Check that no config/env override mangles the token endpoint value.
- Rebuild with the correct upstream URL constant.
Example fix
// before
req, err := http.NewRequest("POST", claudeMaxTokenUrl, bytes.NewReader(body))
// after
req, err := http.NewRequest("POST", claudeMaxTokenUrl, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("token exchange failed - invalid token url %q: %w", claudeMaxTokenUrl, err)
} Defensive patterns
Strategy: validation
Validate before calling
func validURL(s string) bool {
u, err := url.Parse(s)
return err == nil && (u.Scheme == "https" || u.Scheme == "http") && u.Host != ""
}
if !validURL(claudeMaxTokenUrl) { /* fail fast before exchange */ } Type guard
func isRequestCreationErr(err error) bool { return strings.Contains(err.Error(), "error creating request") } Try / catch
tok, err := exchangeCode(ctx, code, verifier)
if err != nil {
if strings.Contains(err.Error(), "error creating request") {
// bad URL constant/config: no point retrying, fix config
}
} Prevention
- Keep token endpoint URLs as compile-time constants, not user input.
- Validate URL scheme/host before constructing requests.
- Don't allow env overrides to blank or corrupt the token URL.
- Add a startup sanity check of all OAuth endpoint URLs.
When it happens
Trigger: http.NewRequest("POST", claudeMaxTokenUrl, bytes.NewReader(body)) returns an error — almost always because the URL constant is malformed (unparseable URL) since body is a valid bytes.Reader.
Common situations: A bad build-time constant or environment override corrupted claudeMaxTokenUrl (e.g. empty string, invalid characters, missing scheme); misconfigured proxy environment variables feeding into a customized URL.
Related errors
- token exchange failed - error reading body: %s
- token exchange failed - status: %d, body: %s
- no stored Claude credentials
- refresh failed - marshal: %w
- refresh failed - create request: %w
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/6468238b6b1495ab.
Report an issue: GitHub.