{"record":{"id":"e25628d720210401","repo":"gastownhall/beads","slug":"batch-update-unsuccessful-single-issue-fallback-a","errorCode":null,"errorMessage":"batch update unsuccessful, single-issue fallback also failed for %s: %w","messagePattern":"batch update unsuccessful, single-issue fallback also failed for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/linear/client.go","lineNumber":1215,"sourceCode":"\t\t\t\tissue, updateErr := c.UpdateIssue(ctx, id, updates)\n\t\t\t\tif updateErr != nil {\n\t\t\t\t\treturn allIssues, fmt.Errorf(\"batch update failed, single-issue fallback also failed for %s: %w (batch error: %v)\", id, updateErr, err)\n\t\t\t\t}\n\t\t\t\tallIssues = append(allIssues, *issue)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tvar batchResp IssueBatchUpdateResponse\n\t\tif err := json.Unmarshal(data, &batchResp); err != nil {\n\t\t\treturn allIssues, fmt.Errorf(\"failed to parse batch update response: %w\", err)\n\t\t}\n\n\t\tif !batchResp.IssueBatchUpdate.Success {\n\t\t\tfor _, id := range chunk {\n\t\t\t\tissue, updateErr := c.UpdateIssue(ctx, id, updates)\n\t\t\t\tif updateErr != nil {\n\t\t\t\t\treturn allIssues, fmt.Errorf(\"batch update unsuccessful, single-issue fallback also failed for %s: %w\", id, updateErr)\n\t\t\t\t}\n\t\t\t\tallIssues = append(allIssues, *issue)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tallIssues = append(allIssues, batchResp.IssueBatchUpdate.Issues...)\n\t}\n\n\treturn allIssues, nil\n}\n\n// FetchIssueByIdentifier retrieves a single issue from Linear by its identifier (e.g., \"TEAM-123\").\n// Returns nil if the issue is not found.\nfunc (c *Client) FetchIssueByIdentifier(ctx context.Context, identifier string) (*Issue, error) {\n\tquery := `\n\t\tquery IssueByIdentifier($filter: IssueFilter!) {\n\t\t\tissues(filter: $filter, first: 1) {","sourceCodeStart":1197,"sourceCodeEnd":1233,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/linear/client.go#L1197-L1233","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\nupdates := map[string]interface{}{\"stateId\": \"stale-state-id\"}\n_, err := client.BatchUpdateIssues(ctx, ids, updates)\n// after\nstate := cache.FindStateForBeadsStatus(types.Open) // resolve a fresh, team-scoped state ID\nif state == \"\" { return fmt.Errorf(\"no valid state for team\") }\nupdates := map[string]interface{}{\"stateId\": state}\n_, err := client.BatchUpdateIssues(ctx, ids, updates)","handlingStrategy":"fallback","validationCode":"// pre-validate IDs and update payload before batching\nfor _, id := range ids {\n    if !strings.HasPrefix(id, \"issue-\") { return fmt.Errorf(\"invalid issue id: %s\", id) }\n}\nif sid, ok := updates[\"stateId\"].(string); ok && cache.FindStateByID(sid) == nil {\n    return fmt.Errorf(\"stateId %q not in team states\", sid)\n}","typeGuard":null,"tryCatchPattern":"all, err := client.BatchUpdateIssues(ctx, ids, updates)\nif err != nil {\n    var parseErr *json.SyntaxError\n    if errors.As(err, &parseErr) { /* decode issue */ }\n    // err wraps the failing issue id; retry remaining ids individually with backoff\n    log.Printf(\"batch update failed: %v\", err)\n    return retryIndividually(ids, updates)\n}","preventionTips":["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."],"tags":["go","graphql","linear-api","batch-update"],"backgroundTag":"graphql-mutation-rejected","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}