{"record":{"id":"3cf4a7d3940aa7bd","repo":"gastownhall/beads","slug":"failed-to-get-issue-for-update-w","errorCode":null,"errorMessage":"failed to get issue for update: %w","messagePattern":"failed to get issue for update: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/update.go","lineNumber":1018,"sourceCode":"\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"%s must be a list of strings, got element %T\", op, item)\n\t\t\t}\n\t\t\tout = append(out, s)\n\t\t}\n\t\treturn out, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"%s must be a list of strings, got %T\", op, value)\n\t}\n}\n\n// readIssueAndResolveMergeOps reads the pre-update row in-transaction and folds\n// any merge-operation keys (metadata edits, note appends) into concrete column\n// values against that row, returning the row and the rewritten update map. It\n// keeps the read-merge-write plumbing off updateIssueInTx's already-large body.\nfunc readIssueAndResolveMergeOps(ctx context.Context, tx DBTX, id string, updates map[string]interface{}) (*types.Issue, map[string]interface{}, error) {\n\toldIssue, err := GetIssueInTx(ctx, tx, id)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to get issue for update: %w\", err)\n\t}\n\tresolved, err := ResolveMergeOps(oldIssue, updates)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn oldIssue, resolved, nil\n}\n\n// RecordFullEventInTable records an event with both old and new values.\nfunc RecordFullEventInTable(ctx context.Context, tx DBTX, table, issueID string, eventType types.EventType, actor, oldValue, newValue string) error {\n\treturn InsertDerivedEvent(ctx, tx, table, AuxEvent{\n\t\tIssueID:   issueID,\n\t\tEventType: eventType,\n\t\tActor:     actor,\n\t\tOldValue:  str(oldValue),\n\t\tNewValue:  str(newValue),\n\t})\n}","sourceCodeStart":1000,"sourceCodeEnd":1036,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/update.go#L1000-L1036","documentation":"readIssueAndResolveMergeOps wraps a failure from GetIssueInTx when it reads the pre-update row inside the mutation transaction. If the current row cannot be loaded, merge operations (metadata edits, note appends) cannot be resolved, so the whole update is aborted with this wrapped error preserving the underlying cause via %w.","triggerScenarios":"Calling updateIssueInTx (via Update/ExecuteUpdate) with a merge-op key in updates while the issue id does not exist, the row was deleted concurrently, or the underlying SELECT fails (connection, permissions, schema).","commonSituations":"Updating an issue that was already deleted in another session; transient DB connection loss mid-transaction; mistyped issue ID; replica/primary mismatch in distributed Dolt setups.","solutions":["Verify the issue ID exists (bd show / GetIssue) before issuing the update","Check errors.Is(err, storage.ErrNotFound) to distinguish a missing issue from a real DB failure","If ErrNotFound, treat as a race: re-fetch state and decide whether to recreate or surface 'issue no longer exists'","Inspect the wrapped cause (%w) for connection/permission problems and retry transient errors"],"exampleFix":"// before\n_, err := store.Update(ctx, id, updates) // panics/logs generically\n// after\n_, err := store.Update(ctx, id, updates)\nif err != nil && errors.Is(err, storage.ErrNotFound) {\n    return fmt.Errorf(\"issue %s no longer exists; refresh and retry\", id)\n}","handlingStrategy":"try-catch","validationCode":"if _, err := store.GetIssue(ctx, id); err != nil { return fmt.Errorf(\"issue %s unavailable before update: %w\", id, err) }","typeGuard":null,"tryCatchPattern":"_, err := store.Update(ctx, id, updates)\nif err != nil {\n    switch {\n    case errors.Is(err, storage.ErrNotFound):\n        return fmt.Errorf(\"issue %s gone; refresh before updating\", id)\n    default:\n        return err // inspect wrapped DB cause\n    }\n}","preventionTips":["Existence-check the issue before merge-op updates","Handle ErrNotFound as a race with deletion, not a bug","Retry only transient DB errors surfaced in the wrapped cause"],"tags":["go","storage","not-found","transaction"],"backgroundTag":"issue-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}