{"record":{"id":"b91462b8d24da3ab","repo":"gastownhall/beads","slug":"set-labels-id-must-not-be-empty","errorCode":null,"errorMessage":"set labels: id must not be empty","messagePattern":"set labels: id must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/label.go","lineNumber":151,"sourceCode":"\t\t}\n\t\tif err := u.labelRepo.Delete(ctx, id, label, actor, opts); err != nil {\n\t\t\treturn fmt.Errorf(\"remove labels: %s: %w\", label, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (u *labelUseCaseImpl) SetLabels(ctx context.Context, issueID string, labels []string, actor string) error {\n\treturn u.setMany(ctx, issueID, labels, actor, false)\n}\n\nfunc (u *labelUseCaseImpl) SetWispLabels(ctx context.Context, wispID string, labels []string, actor string) error {\n\treturn u.setMany(ctx, wispID, labels, actor, true)\n}\n\nfunc (u *labelUseCaseImpl) setMany(ctx context.Context, id string, labels []string, actor string, useWisp bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"set labels: id must not be empty\")\n\t}\n\topts := LabelOpts{UseWispsTable: useWisp}\n\tcurrent, err := u.labelRepo.List(ctx, id, opts)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"set labels: list current: %w\", err)\n\t}\n\tdesired := make(map[string]bool, len(labels))\n\tfor _, l := range labels {\n\t\tif l != \"\" {\n\t\t\tdesired[l] = true\n\t\t}\n\t}\n\texisting := make(map[string]bool, len(current))\n\tfor _, l := range current {\n\t\texisting[l] = true\n\t\tif !desired[l] {\n\t\t\tif err := u.labelRepo.Delete(ctx, id, l, actor, opts); err != nil {\n\t\t\t\treturn fmt.Errorf(\"set labels: remove %s: %w\", l, err)","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/label.go#L133-L169","documentation":"Thrown by setMany (SetLabels / SetWispLabels) when the ID is empty. SetLabels replaces the full label set, so it must first know which record to operate on; an empty ID is rejected before listing current labels. Input validation, not a storage error.","triggerScenarios":"SetLabels(ctx, \"\", labels, actor) or SetWispLabels(ctx, \"\", labels, actor); an ID variable left empty by a failed creation, missing CLI flag, or JSON field omission.","commonSituations":"Sync tools computing a desired label set for records that were never created; template pipelines where the ID placeholder did not interpolate; renaming refactor left an ID field unset.","solutions":["Create/locate the issue first and use its returned ID","Guard the call with a non-empty ID check","Fix the upstream producer of the ID variable","For wisps, pass the wisp ID to SetWispLabels"],"exampleFix":"// before\nstore.SetLabels(ctx, cfg.IssueID, desired, actor) // IssueID often \"\"\n// after\nif cfg.IssueID == \"\" {\n\treturn fmt.Errorf(\"IssueID not set in config\")\n}\nstore.SetLabels(ctx, cfg.IssueID, desired, actor)","handlingStrategy":"validation","validationCode":"if id == \"\" {\n\treturn fmt.Errorf(\"cannot set labels: issue id is empty\")\n}\nif err := store.SetLabels(ctx, id, labels, actor); err != nil { ... }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Create/locate the issue before setting labels and use its returned ID","Validate config fields that supply IDs are populated","Never hardcode empty placeholders in generated code paths","Match wisp APIs to wisp IDs"],"tags":["validation","labels","empty-id"],"backgroundTag":"empty-required-identifier","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}