gastownhall/beads · error

marshal create request: %w

Error message

marshal create request: %w

What it means

CreateIssue wraps json.Marshal failures when encoding {"fields": ...} from the caller-supplied map. json.Marshal of a map[string]interface{} only fails for values JSON cannot represent: channels, funcs, complex numbers, or cyclic references. The library throws it because the request payload cannot be built at all.

Source

Thrown at internal/jira/client.go:258

	if err != nil {
		return nil, fmt.Errorf("get issue %s: %w", key, err)
	}

	var issue Issue
	if err := json.Unmarshal(body, &issue); err != nil {
		return nil, fmt.Errorf("parse issue response: %w", err)
	}

	return &issue, nil
}

// CreateIssue creates a new issue in Jira.
// fields should include "project", "summary", "issuetype", and optionally other fields.
func (c *Client) CreateIssue(ctx context.Context, fields map[string]interface{}) (*Issue, error) {
	payload := map[string]interface{}{"fields": fields}
	data, err := json.Marshal(payload)
	if err != nil {
		return nil, fmt.Errorf("marshal create request: %w", err)
	}

	apiURL := fmt.Sprintf("%s/issue", c.apiBase())

	body, err := c.doRequest(ctx, "POST", apiURL, data)
	if err != nil {
		return nil, fmt.Errorf("create issue: %w", err)
	}

	// Create response only returns id, key, self. Fetch the full issue.
	var created struct {
		ID   string `json:"id"`
		Key  string `json:"key"`
		Self string `json:"self"`
	}
	if err := json.Unmarshal(body, &created); err != nil {
		return nil, fmt.Errorf("parse create response: %w", err)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Audit the fields map values: remove or convert channels, funcs, and complex types to JSON-safe values.
  2. Use json.RawMessage or explicit structs for custom/complex field values.
  3. Marshal the fields map alone in a test to find the offending key quickly.
  4. Prefer typed structs over map[string]interface{} for request bodies so invalid values fail at compile time.

Example fix

// before: unsupported value slips in
fields := map[string]interface{}{
    "project":     map[string]string{"key": "PROJ"},
    "customfield": someChannel, // json: unsupported type
}
// after: only JSON-encodable values
fields := map[string]interface{}{
    "project":     map[string]string{"key": "PROJ"},
    "customfield": "string-value",
}
Defensive patterns

Strategy: validation

Validate before calling

// Fail fast on non-serializable values before calling the API:
func validateFields(fields map[string]interface{}) error {
    for k, v := range fields {
        switch v.(type) {
        case chan interface{}, func(), complex128, map[interface{}]interface{}:
            return fmt.Errorf("field %q has non-JSON-encodable type %T", k, v)
        }
    }
    _, err := json.Marshal(fields)
    return err
}

Type guard

func jsonEncodable(v interface{}) bool {
    _, err := json.Marshal(v)
    return err == nil
}

Try / catch

issue, err := client.CreateIssue(ctx, fields)
if err != nil && strings.Contains(err.Error(), "marshal create request") {
    // non-serializable value in fields: no request was sent, safe to fix and retry
    for k, v := range fields {
        if !jsonEncodable(v) {
            log.Printf("field %q (%T) is not JSON-encodable", k, v)
        }
    }
    return
}

Prevention

When it happens

Trigger: Passing a fields map containing a non-serializable value (e.g. a func, channel, or a custom struct with unexported-only/cyclic data) as any field value in CreateIssue.

Common situations: Building fields programmatically where a variable of type interface{} holds an unsupported runtime type; accidental inclusion of a logger or callback in the map; custom field values sourced from reflection-heavy code.

Related errors


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