{"record":{"id":"a8d14b030840e208","repo":"gastownhall/beads","slug":"db-labelsqlrepository-insert-issueid-must-not-be","errorCode":null,"errorMessage":"db: LabelSQLRepository.Insert: issueID must not be empty","messagePattern":"db: LabelSQLRepository\\.Insert: issueID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/label.go","lineNumber":37,"sourceCode":"}\n\ntype labelSQLRepositoryImpl struct {\n\trunner Runner\n\tevents domain.EventsSQLRepository\n}\n\nvar _ domain.LabelSQLRepository = (*labelSQLRepositoryImpl)(nil)\n\nfunc pickLabelTable(useWisps bool) string {\n\tif useWisps {\n\t\treturn \"wisp_labels\"\n\t}\n\treturn \"labels\"\n}\n\nfunc (r *labelSQLRepositoryImpl) Insert(ctx context.Context, issueID, label, actor string, opts domain.LabelOpts) error {\n\tif issueID == \"\" {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Insert: issueID must not be empty\")\n\t}\n\tif label == \"\" {\n\t\treturn fmt.Errorf(\"db: LabelSQLRepository.Insert: label must not be empty\")\n\t}\n\t// Reject an over-length label before the INSERT IGNORE, which would otherwise\n\t// silently truncate it to the VARCHAR(255) column. This is the proxied-server\n\t// (uow) analog of issueops.AddLabelInTx's guard, so both write stacks return a\n\t// typed ErrFieldTooLong instead of storing a label the caller never sent.\n\tif err := types.CheckFieldLen(\"label\", label); err != nil {\n\t\treturn err\n\t}\n\ttable := pickLabelTable(opts.UseWispsTable)\n\t//nolint:gosec // G201: table is one of two hardcoded constants\n\tresult, err := r.runner.ExecContext(ctx,\n\t\tfmt.Sprintf(\"INSERT IGNORE INTO %s (issue_id, label) VALUES (?, ?)\", table),\n\t\tissueID, label,\n\t)\n\tif err != nil {","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/label.go#L19-L55","documentation":"Input validation error from LabelSQLRepository.Insert: the issueID argument is the empty string. The SQL-layer repository refuses to issue an INSERT for a label without knowing which issue it attaches to, failing fast before touching the database.","triggerScenarios":"Calling Insert(ctx, \"\", label, actor, opts) directly, or via a proxy/uow write stack where the caller's issue was never assigned an ID (e.g. added before creation/flush).","commonSituations":"Adding a label to a not-yet-persisted issue, an ID field lost in a struct copy, or a proxied-server request missing the issue identifier.","solutions":["Ensure the issue is created and its ID populated before calling Insert","Fix the caller to pass the real issue ID","Validate issueID non-empty at your API boundary","Check for ID propagation bugs in uow/proxy layers"],"exampleFix":"// before\nrepo.Insert(ctx, issue.ID, \"bug\", actor, opts) // issue.ID == \"\"\n// after\nif issue.ID == \"\" {\n    return fmt.Errorf(\"issue not persisted\")\n}\nrepo.Insert(ctx, issue.ID, \"bug\", actor, opts)","handlingStrategy":"validation","validationCode":"func validateLabelInput(issueID, label string) error {\n    if issueID == \"\" { return fmt.Errorf(\"issueID required\") }\n    if label == \"\" { return fmt.Errorf(\"label required\") }\n    return nil\n}","typeGuard":"func hasIssueID(i *types.Issue) bool { return i != nil && i.ID != \"\" }","tryCatchPattern":"if err := repo.Insert(ctx, issueID, label, actor, opts); err != nil {\n    if strings.Contains(err.Error(), \"issueID must not be empty\") {\n        return fmt.Errorf(\"cannot label unpersisted issue %q\", issueID)\n    }\n    return err\n}","preventionTips":["Never attach labels before the issue is created and flushed","Validate IDs at the API boundary","Check struct-copy sites that may drop the ID field","Cover the label-before-create order bug with a regression test"],"tags":["validation","labels","guard-clause"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}