gastownhall/beads · error

create issue

Error message

create issue: %w

What it means

CreateIssue wraps doRequest errors for POST /issue. The issue-creation HTTP call failed: validation error (400), auth/permission failure (401/403), or transport problem. The library throws it so callers know no issue was created (or creation was rejected) before any hydration happens.

Solutions

  1. Check the wrapped error's HTTP status and Jira error messages: 400 responses list which fields are invalid.
  2. Verify project key and issuetype name exactly match the instance (query createmeta if unsure).
  3. For 401/403, fix the API token and confirm the account has Create Issues permission in the target project.
  4. If 429, add backoff/jitter between creations.
  5. Note the issue may have been created despite later errors — check Jira before retrying blindly.

Example fix

// before
issue, err := client.CreateIssue(ctx, map[string]interface{}{
    "project": map[string]string{"key": "PROJ"},
    "summary": "t", // missing issuetype -> 400
})
// after
issue, err := client.CreateIssue(ctx, map[string]interface{}{
    "project":   map[string]string{"key": "PROJ"},
    "summary":   "Fix login failure",
    "issuetype": map[string]string{"name": "Bug"},
})
Defensive patterns

Strategy: validation

Validate before calling

// Validate required fields and permissions before POST:
func validCreateFields(fields map[string]interface{}) error {
    for _, req := range []string{"project", "summary", "issuetype"} {
        if _, ok := fields[req]; !ok {
            return fmt.Errorf("missing required field %q", req)
        }
    }
    return nil
}
// Preflight: GET /rest/api/3/createmeta?projectKeys=PROJ to confirm issuetype names

Type guard

func isPermissionDenied(err error) bool {
    return err != nil && strings.Contains(err.Error(), "403")
}

Try / catch

issue, err := client.CreateIssue(ctx, fields)
if err != nil {
    if strings.Contains(err.Error(), "400") {
        // Jira's error body lists invalid fields; log and fix fields before retry
        log.Printf("create rejected: %v — check project key, issuetype name, custom fields", err)
        return
    }
    if isPermissionDenied(err) {
        return fmt.Errorf("account lacks create permission in project: %w", err)
    }
    // 429/5xx/timeouts: retry with backoff, but check for partial creation first
    return retryWithBackoff(func() error { _, err = client.CreateIssue(ctx, fields); return err })
}

Prevention

When it happens

Trigger: POST /issue rejected with 400 because required fields are missing/invalid (project key, issuetype name, summary), 401/403 from bad token or no create permission, 429 rate limiting, or network failure.

Common situations: Wrong project key or issuetype name (e.g. "Bug" vs "Task" vs localized names); account lacking create-issue permission in the project; expired API token; mandatory custom fields not supplied; bulk creation tripping rate limits.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at internal/jira/client.go:265

	}

	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)
	}

	return c.GetIssue(ctx, created.Key)
}

// UpdateIssue updates an existing Jira issue by key.
func (c *Client) UpdateIssue(ctx context.Context, key string, fields map[string]interface{}) error {
	payload := map[string]interface{}{"fields": fields}

View on GitHub (pinned to 71377f2769)