{"record":{"id":"5ce98f094a6271f2","repo":"gastownhall/beads","slug":"add-label-s-s-w","errorCode":null,"errorMessage":"add label %s/%s: %w","messagePattern":"add label (.+?)/(.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/label.go","lineNumber":67,"sourceCode":"var _ LabelUseCase = (*labelUseCaseImpl)(nil)\n\nfunc (u *labelUseCaseImpl) AddLabel(ctx context.Context, issueID, label, actor string) error {\n\treturn u.add(ctx, issueID, label, actor, false)\n}\n\nfunc (u *labelUseCaseImpl) AddWispLabel(ctx context.Context, wispID, label, actor string) error {\n\treturn u.add(ctx, wispID, label, actor, true)\n}\n\nfunc (u *labelUseCaseImpl) add(ctx context.Context, id, label, actor string, useWisp bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"add label: id must not be empty\")\n\t}\n\tif label == \"\" {\n\t\treturn fmt.Errorf(\"add label: label must not be empty\")\n\t}\n\tif err := u.labelRepo.Insert(ctx, id, label, actor, LabelOpts{UseWispsTable: useWisp}); err != nil {\n\t\treturn fmt.Errorf(\"add label %s/%s: %w\", id, label, err)\n\t}\n\treturn nil\n}\n\nfunc (u *labelUseCaseImpl) RemoveLabel(ctx context.Context, issueID, label, actor string) error {\n\treturn u.remove(ctx, issueID, label, actor, false)\n}\n\nfunc (u *labelUseCaseImpl) RemoveWispLabel(ctx context.Context, wispID, label, actor string) error {\n\treturn u.remove(ctx, wispID, label, actor, true)\n}\n\nfunc (u *labelUseCaseImpl) remove(ctx context.Context, id, label, actor string, useWisp bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"remove label: id must not be empty\")\n\t}\n\tif label == \"\" {\n\t\treturn fmt.Errorf(\"remove label: label must not be empty\")","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/label.go#L49-L85","documentation":"Wraps an error returned by labelRepo.Insert when adding a label to an issue or wisp fails at the storage layer. The message embeds the issue ID and label (\"add label %s/%s\") with the underlying cause via %w. This fires only after the empty-ID/empty-label guards pass.","triggerScenarios":"Calling AddLabel/AddWispLabel with valid non-empty arguments where Insert fails: nonexistent issue ID (foreign-key style failure), duplicate label insert, database locked/unavailable, or context cancellation.","commonSituations":"Typo'd or stale issue IDs referencing deleted issues; adding the same label concurrently from two processes; store file locked by another bd instance; DB connection dropped mid-command.","solutions":["Read the wrapped cause: if it's a not-found/constraint error, verify the issue ID exists (bd show <id>).","Check for duplicate-label handling — treat 'already exists' errors as success if idempotency is desired.","Ensure no other process holds the store lock; retry after the conflicting command finishes.","Retry with a fresh context if cancellation or a transient connection error is reported.","Use AddLabels (addMany) for batch adds, which tolerates skips more gracefully."],"exampleFix":"// before\nerr := labelUC.AddLabel(ctx, id, label, actor)\n\n// after: tolerate duplicates explicitly\nerr := labelUC.AddLabel(ctx, id, label, actor)\nif err != nil && strings.Contains(err.Error(), \"already exists\") {\n    err = nil // idempotent add\n}","handlingStrategy":"try-catch","validationCode":"// verify the issue exists before inserting a label\nif _, err := issueUC.GetIssue(ctx, id); err != nil {\n    return fmt.Errorf(\"issue %s not found, cannot add label: %w\", id, err)\n}","typeGuard":"func isLabelInsertErr(err error) bool {\n    return err != nil && strings.HasPrefix(err.Error(), \"add label \")\n}","tryCatchPattern":"err := uc.AddLabel(ctx, id, label, actor)\nif err != nil {\n    cause := errors.Unwrap(err)\n    if isAlreadyExists(cause) { return nil } // idempotent\n    if isNotFound(cause) { return fmt.Errorf(\"issue %s gone\", id) }\n    return err\n}","preventionTips":["Confirm the target issue exists before labeling","Design label adds to be idempotent (treat duplicate errors as success)","Avoid concurrent writers on the same store","Retry transient storage errors with backoff"],"tags":["storage","database","labels","insert"],"backgroundTag":"database-insert-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}