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

  1. Treat it as a bug: if it reproduces with unmodified code, file an issue with the full wrapped error and Go version
  2. Check for local patches or test hooks overriding encoding/json behavior
  3. If you modified the request body to include non-marshalable types (channels, funcs, cyclic pointers), fix or remove them
  4. 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

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


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/90e96b138628ea4a. Report an issue: GitHub.