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.OauthResponse

View on GitHub (pinned to e2d772072e)

Solutions

  1. Inspect the wrapped error; it names the URL parse failure.
  2. Verify claudeMaxTokenUrl is a valid absolute https URL (non-empty, with scheme).
  3. Check that no config/env override mangles the token endpoint value.
  4. 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

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


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/6468238b6b1495ab. Report an issue: GitHub.