gastownhall/beads · error

failed to marshal request: %w

Error message

failed to marshal request: %w

What it means

executeOnce serializes the GraphQLRequest to JSON before sending it. If json.Marshal fails — which for well-formed structs is rare but possible with unsupported values (e.g. channels, funcs, or NaN-like numbers injected via custom types) — the client returns 'failed to marshal request' wrapped around the marshal error before any HTTP call is made.

Source

Thrown at internal/linear/client.go:354

	if statusCode == http.StatusUnauthorized && c.AuthMode == AuthModeOAuth {
		debug.Logf("oauth: received 401, invalidating token and retrying")
		c.TokenManager.Invalidate()
		data, _, retryErr := c.executeOnce(ctx, req)
		if retryErr != nil {
			return nil, retryErr
		}
		return data, nil
	}

	return nil, err
}

// executeOnce performs the actual HTTP request loop with rate-limit retries.
// Returns the response data, the last HTTP status code encountered, and any error.
func (c *Client) executeOnce(ctx context.Context, req *GraphQLRequest) (json.RawMessage, int, error) {
	body, err := json.Marshal(req)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to marshal request: %w", err)
	}

	var lastErr error
	var lastStatus int
	for attempt := 0; attempt <= MaxRetries; attempt++ {
		if rlErr := c.circuitBreakerError(); rlErr != nil {
			return nil, lastStatus, rlErr
		}

		httpReq, err := http.NewRequestWithContext(ctx, "POST", c.Endpoint, bytes.NewReader(body))
		if err != nil {
			return nil, 0, fmt.Errorf("failed to create request: %w", err)
		}

		httpReq.Header.Set("Content-Type", "application/json")
		authValue, err := c.authHeader()
		if err != nil {
			return nil, 0, err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error to identify the offending field/type in the request variables
  2. Fix or remove the value that cannot be marshaled (cycle, unsupported type, invalid RawMessage)
  3. Validate custom MarshalJSON implementations on variable types
  4. Marshal the variables yourself beforehand to localize the failure before calling Execute

Example fix

// before
req := &linear.GraphQLRequest{Query: q, Variables: map[string]interface{}{"x": someChan}}
// after
vars, err := json.Marshal(map[string]interface{}{"x": "supported-value"})
if err != nil { return err } // catch it before the client does
req := &linear.GraphQLRequest{Query: q, Variables: parsedVars}
Defensive patterns

Strategy: validation

Validate before calling

if raw, err := json.Marshal(req); err != nil {
	return fmt.Errorf("request not serializable: %w", err)
} else if !json.Valid(raw) {
	return fmt.Errorf("request marshaled to invalid JSON")
}

Type guard

func isMarshalable(v interface{}) bool {
	var mErr error
	_ = json.Marshal // probe; real check:
	mErr = marshalErr(v)
	return mErr == nil
}
func marshalErr(v interface{}) error {
	b, err := json.Marshal(v)
	_ = b
	return err
}

Try / catch

data, err := client.Execute(ctx, req)
if err != nil {
	if strings.Contains(err.Error(), "failed to marshal request") {
		return fmt.Errorf("bug: request variables unsupported by encoding/json: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling Execute with a GraphQLRequest whose Variables (or other fields) contain values json.Marshal cannot encode: json.RawMessage holding invalid JSON, cyclic data structures, unsupported types (chan, func, complex), or NaN/Inf float values.

Common situations: Passing variables built from dynamic data with cycles; injecting json.RawMessage from another marshal step that produced invalid bytes; custom types with a broken MarshalJSON implementation returning an error.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/30ca6ec06b52b896. Report an issue: GitHub.