gastownhall/beads · error
batch update unsuccessful, single-issue fallback also failed
Error message
batch update unsuccessful, single-issue fallback also failed for %s: %w
What it means
BatchUpdateIssues sends chunked issueBatchUpdate mutations; when Linear reports success=false for a chunk, the client retries each issue individually via UpdateIssue. This error wraps the failure of that per-issue fallback, meaning both the batch mutation was rejected AND the single-issue update for this specific ID also failed.
Source
Thrown at internal/linear/client.go:1215
issue, updateErr := c.UpdateIssue(ctx, id, updates)
if updateErr != nil {
return allIssues, fmt.Errorf("batch update failed, single-issue fallback also failed for %s: %w (batch error: %v)", id, updateErr, err)
}
allIssues = append(allIssues, *issue)
}
continue
}
var batchResp IssueBatchUpdateResponse
if err := json.Unmarshal(data, &batchResp); err != nil {
return allIssues, fmt.Errorf("failed to parse batch update response: %w", err)
}
if !batchResp.IssueBatchUpdate.Success {
for _, id := range chunk {
issue, updateErr := c.UpdateIssue(ctx, id, updates)
if updateErr != nil {
return allIssues, fmt.Errorf("batch update unsuccessful, single-issue fallback also failed for %s: %w", id, updateErr)
}
allIssues = append(allIssues, *issue)
}
continue
}
allIssues = append(allIssues, batchResp.IssueBatchUpdate.Issues...)
}
return allIssues, nil
}
// FetchIssueByIdentifier retrieves a single issue from Linear by its identifier (e.g., "TEAM-123").
// Returns nil if the issue is not found.
func (c *Client) FetchIssueByIdentifier(ctx context.Context, identifier string) (*Issue, error) {
query := `
query IssueByIdentifier($filter: IssueFilter!) {
issues(filter: $filter, first: 1) {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped updateErr (use %v/%+v or errors.Unwrap) to see why the single-issue update failed — it is the root cause.
- Verify every key/value in the updates map is valid for the target team (state IDs, label IDs, assignee IDs).
- Re-run BatchUpdateIssues: issues earlier in the chunk may have been updated already; the library does not roll them back.
- Check the Linear API token has write scopes for the affected team/workspace.
- If the batch consistently fails but singles succeed, reduce BatchSize or check for Linear-side validation rules blocking batched mutations.
Example fix
// before
updates := map[string]interface{}{"stateId": "stale-state-id"}
_, err := client.BatchUpdateIssues(ctx, ids, updates)
// after
state := cache.FindStateForBeadsStatus(types.Open) // resolve a fresh, team-scoped state ID
if state == "" { return fmt.Errorf("no valid state for team") }
updates := map[string]interface{}{"stateId": state}
_, err := client.BatchUpdateIssues(ctx, ids, updates) Defensive patterns
Strategy: fallback
Validate before calling
// pre-validate IDs and update payload before batching
for _, id := range ids {
if !strings.HasPrefix(id, "issue-") { return fmt.Errorf("invalid issue id: %s", id) }
}
if sid, ok := updates["stateId"].(string); ok && cache.FindStateByID(sid) == nil {
return fmt.Errorf("stateId %q not in team states", sid)
} Try / catch
all, err := client.BatchUpdateIssues(ctx, ids, updates)
if err != nil {
var parseErr *json.SyntaxError
if errors.As(err, &parseErr) { /* decode issue */ }
// err wraps the failing issue id; retry remaining ids individually with backoff
log.Printf("batch update failed: %v", err)
return retryIndividually(ids, updates)
} Prevention
- Resolve state/label/assignee IDs from a fresh cache (BuildStateCache) instead of hardcoding them.
- Ensure updates keys only contain fields valid for IssueUpdateInput.
- Check token write scopes before sync jobs.
- Treat the error as partial: issues before the failing ID in the chunk may already be updated; don't blindly re-run if updates are non-idempotent.
- Log updateErr with %+v to capture the root cause chain.
When it happens
Trigger: Linear accepted the GraphQL request (Execute succeeded) but returned IssueBatchUpdate.Success=false — e.g. an invalid state ID, label ID, or field value in the updates map — and then UpdateIssue(ctx, id, updates) also returned an error (invalid ID, permission denied, another GraphQL error) for the issue being retried.
Common situations: Syncing issues to a state ID that was archived or belongs to a different team; passing label IDs from another workspace; API token lacking write permission to the team; partial failure mid-chunk so earlier issues in the chunk were already updated individually (partial application).
Related errors
- failed to fetch issue by identifier: %w
- failed to fetch teams: %w
- failed to fetch projects: %w
- failed to create project: %w
- project creation reported as unsuccessful
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e25628d720210401.
Report an issue: GitHub.