{"record":{"id":"88f383fb7cb2c08c","repo":"gastownhall/beads","slug":"w-bootstrap-requires-a-non-empty-issue-prefix","errorCode":null,"errorMessage":"%w: bootstrap requires a non-empty issue prefix","messagePattern":"%w: bootstrap requires a non-empty issue prefix","errorType":"validation","errorClass":"issueops.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/workapi/bootstrap.go","lineNumber":53,"sourceCode":"//\n// The settings plane strips a trailing hyphen from this key. Doing it here\n// instead of relying on that means BootstrapResult.Prefix and the value a later\n// VerifyIdentity answers with are one value on every backend, including the\n// unit-of-work one whose write does not pass through that plane's normalizer.\nfunc NormalizeBootstrapPrefix(prefix string) string {\n\treturn strings.TrimRight(prefix, \"-\")\n}\n\n// ValidateBootstrapRequest refuses a bootstrap that cannot produce a usable\n// workspace and returns the request as it will be WRITTEN.\n//\n// Every implementation validates through it BEFORE opening a transaction, which\n// is what makes issueops.Bootstrapper's \"a refusal writes nothing\" true of the\n// connection as well as of the keys.\nfunc ValidateBootstrapRequest(req issueops.BootstrapRequest) (issueops.BootstrapRequest, error) {\n\treq.Prefix = NormalizeBootstrapPrefix(req.Prefix)\n\tif req.Prefix == \"\" {\n\t\treturn issueops.BootstrapRequest{}, fmt.Errorf(\"%w: bootstrap requires a non-empty issue prefix\", issueops.ErrValidation)\n\t}\n\tif req.ProjectID == \"\" {\n\t\treturn issueops.BootstrapRequest{}, fmt.Errorf(\"%w: bootstrap requires a project id; this role does not mint one\", issueops.ErrValidation)\n\t}\n\treturn req, nil\n}\n\n// RefuseIdentifiedSubstrate turns the pair a bootstrap read into the refusal\n// issueops.Bootstrapper promises, or nil when the substrate is free.\n//\n// EITHER MARKER IS ENOUGH. A substrate several rigs share carries a prefix one\n// of them chose, and a bootstrap that overwrote it would rename every id the\n// others are about to mint; a substrate carrying only a project id is either a\n// server-provisioned one bd must adopt rather than stamp, or a bootstrap that\n// failed partway, and bd cannot tell those apart. The error carries both values\n// so its caller can.\nfunc RefuseIdentifiedSubstrate(prefix, projectID string) error {\n\tif prefix == \"\" && projectID == \"\" {","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/bootstrap.go#L35-L71","documentation":"ValidateBootstrapRequest wraps issueops.ErrValidation when the bootstrap request's issue prefix is empty after normalization. Bootstrap mints issues under a prefix, so an empty prefix would create unnameable issues; the validator runs before any transaction opens, guaranteeing \"a refusal writes nothing\".","triggerScenarios":"Calling any issueops Bootstrapper implementation with BootstrapRequest{Prefix: \"\"} (or a prefix that normalizes to empty, e.g. only a hyphen \"-\").","commonSituations":"Config file missing the prefix key; environment variable interpolation producing an empty string; YAML value set to just \"-\" which NormalizeBootstrapPrefix strips.","solutions":["Set a non-empty prefix on the BootstrapRequest, e.g. Prefix: \"bd\"","Check the config/env source feeding Prefix for empty or hyphen-only values","Check errors.Is(err, issueops.ErrValidation) to distinguish this refusal from backend failures"],"exampleFix":"// before\nreq := issueops.BootstrapRequest{Prefix: cfg.IssuePrefix, ProjectID: cfg.ProjectID}\n// after\nif strings.Trim(cfg.IssuePrefix, \"-\") == \"\" { return fmt.Errorf(\"config issuePrefix must be non-empty\") }\nreq := issueops.BootstrapRequest{Prefix: cfg.IssuePrefix, ProjectID: cfg.ProjectID}","handlingStrategy":"validation","validationCode":"if strings.Trim(req.Prefix, \"-\") == \"\" {\n    return fmt.Errorf(\"bootstrap prefix must be set (got %q)\", req.Prefix)\n}","typeGuard":null,"tryCatchPattern":"normalized, err := workapi.ValidateBootstrapRequest(req)\nif errors.Is(err, issueops.ErrValidation) {\n    return fmt.Errorf(\"config bootstrap error: %w\", err)\n}","preventionTips":["Load prefix from a single validated config source","Fail fast at startup if prefix config is empty","Trim hyphens yourself before submitting to match normalization"],"tags":["validation","bootstrap","prefix","issueops"],"backgroundTag":"bootstrap-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}