gastownhall/beads · error

failed to update issue: %w

Error message

failed to update issue: %w

What it means

UpdateIssue builds an issueUpdate GraphQL mutation and executes it via c.Execute; if Execute returns any error (transport failure, HTTP non-2xx, GraphQL errors, parse failure), it is wrapped as 'failed to update issue'. This is the top-level wrapper for all update-path failures; unwrap it to find the root cause.

Source

Thrown at internal/linear/client.go:1010

						type
					}
					updatedAt
				}
			}
		}
	`

	req := &GraphQLRequest{
		Query: query,
		Variables: map[string]interface{}{
			"id":    issueID,
			"input": updates,
		},
	}

	data, err := c.Execute(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("failed to update issue: %w", err)
	}

	var updateResp IssueUpdateResponse
	if err := json.Unmarshal(data, &updateResp); err != nil {
		return nil, fmt.Errorf("failed to parse update response: %w", err)
	}

	if !updateResp.IssueUpdate.Success {
		return nil, fmt.Errorf("issue update reported as unsuccessful")
	}

	return &updateResp.IssueUpdate.Issue, nil
}

// BatchCreateIssues creates multiple issues in Linear using the issueBatchCreate mutation.
// Inputs are chunked into groups of BatchSize (50).
//
// On ambiguous failure (API error or success=false), this method does NOT blindly

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap with errors.As/Unwrap to see whether it's transport, HTTP status, or a GraphQL error
  2. Verify issueID and all update field values (stateID, assigneeID, labelIDs) are current and in the same workspace
  3. Handle 429 by throttling bulk updates with backoff
  4. Ensure the API token has write scopes for issue updates

Example fix

// before
_, err := client.UpdateIssue(ctx, issueID, linear.IssueUpdateInput{StateID: cachedStateID})
// after
stateID := refreshStateIDFromAPI(ctx, client, teamID) // don't trust stale cached IDs
_, err := client.UpdateIssue(ctx, issueID, linear.IssueUpdateInput{StateID: stateID})
if err != nil {
    var ge *linear.GraphQLError
    if errors.As(err, &ge) { /* handle field-specific GraphQL error */ }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the issue still exists and IDs are valid before updating
issues, err := client.SearchIssues(ctx, fmt.Sprintf("id: %s", issueID))
if err != nil || len(issues) == 0 {
    return fmt.Errorf("issue %s not found, skipping update", issueID)
}

Try / catch

_, err := client.UpdateIssue(ctx, issueID, updates)
if err != nil {
    unwrapped := fmt.Errorf("%v", errors.Unwrap(err))
    switch {
    case strings.Contains(unwrapped.Error(), "status 429"):
        backoffAndRetry()
    case strings.Contains(unwrapped.Error(), "GraphQL errors"):
        log.Printf("update rejected, check IDs/scopes: %v", unwrapped)
    default:
        return err
    }
}

Prevention

When it happens

Trigger: Invalid issueID (nonexistent/archived issue) producing a GraphQL error; trying to set an invalid stateID/label/assignee in updates; network failure or 429 rate limit during the mutation; token lacking update scope.

Common situations: Issue deleted or moved to trash by a user while the automation was running; stale IDs cached from earlier queries; bulk update loops tripping Linear's rate limiter; integration tokens without write scope.

Related errors


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