charmbracelet/crush · warning
marshal request: %w
Error message
marshal request: %w
What it means
This error wraps a failure of json.Marshal when serializing the {"refresh_token": ...} request body inside ExchangeToken. With a map[string]string containing a single string value, marshalling essentially cannot fail in practice, so hitting this error indicates an extraordinary encoding-layer fault rather than bad input. It is a defensive wrapper for consistency with the package's other error paths.
Source
Thrown at internal/oauth/hyper/device.go:160
return result, fmt.Errorf("unmarshal response: %w: %s", err, string(body))
}
if resp.StatusCode != http.StatusOK {
return result, fmt.Errorf("token request failed: status %d body %q", resp.StatusCode, string(body))
}
return result, nil
}
// ExchangeToken exchanges a refresh token for an access token.
func ExchangeToken(ctx context.Context, refreshToken string) (*oauth.Token, error) {
reqBody := map[string]string{
"refresh_token": refreshToken,
}
data, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("marshal request: %w", err)
}
url := hyper.BaseURL() + "/token/exchange"
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "crush")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("execute request: %w", err)
}
defer resp.Body.Close()
View on GitHub (pinned to 7944b8e522)
Solutions
- Treat it as a bug: if it reproduces with unmodified code, file an issue with the full wrapped error and Go version
- Check for local patches or test hooks overriding encoding/json behavior
- If you modified the request body to include non-marshalable types (channels, funcs, cyclic pointers), fix or remove them
- Ensure the refreshToken value contains no exotic types (it must be a plain string) and that callers pass a normal string
Example fix
// before: marshalling a dynamic map that could theoretically hold non-marshalable values
reqBody := map[string]string{
"refresh_token": refreshToken,
}
data, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("marshal request: %w", err)
}
// after: a fixed struct makes marshal failure provably impossible
reqBody := struct {
RefreshToken string `json:"refresh_token"`
}{RefreshToken: refreshToken}
data, err := json.Marshal(reqBody)
if err != nil {
return nil, fmt.Errorf("marshal request: %w", err)
} Defensive patterns
Strategy: type-guard
Validate before calling
// json.Marshal of a plain string cannot fail, but guard the input anyway:
func validateRefreshToken(refreshToken string) error {
if strings.TrimSpace(refreshToken) == "" {
return errors.New("refresh token is empty")
}
return nil
}
// call before ExchangeToken:
if err := validateRefreshToken(refreshToken); err != nil {
return nil, err
} Type guard
func isMarshalError(err error) bool {
var jsonErr *json.UnsupportedTypeError
return err != nil && (strings.HasPrefix(err.Error(), "marshal request:") || errors.As(err, &jsonErr))
} Try / catch
token, err := hyper.ExchangeToken(ctx, refreshToken)
if err != nil {
if strings.HasPrefix(err.Error(), "marshal request:") {
// Practically unreachable with a plain string map; treat as a bug.
return nil, fmt.Errorf("unexpected request encoding failure: %w", err)
}
return nil, err
} Prevention
- Always pass a plain, non-empty string as the refresh token
- If extending the request body, only add marshalable types (strings, structs)
- Use a fixed struct instead of a map for request bodies to catch issues at compile time
- Treat any occurrence of this error in unmodified code as a bug and report it
When it happens
Trigger: json.Marshal(reqBody) returns an error while encoding the refresh-token map in ExchangeToken (called by loginHyper, exchange, and an anonymous caller). Given the input is always a valid map[string]string, a trigger would require a corrupted encoding environment (e.g. custom json.MarshalOverride / patched encoding/json) or a code change introducing a non-marshalable value into the request body.
Common situations: Practically never seen in the field; most likely encountered after local modifications to ExchangeToken that add channels/funcs/cyclic structures to the request body, or a broken custom JSON encoder injected in tests.
Related errors
- failed to marshal api key string: %w
- failed to marshal oauth token: %w
- failed to marshal schema: %w
- failed to marshal provider data: %w
- failed to decode response: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/90e96b138628ea4a.
Report an issue: GitHub.