{"record":{"id":"6e26ee7010e0cd4f","repo":"gastownhall/beads","slug":"q-is-set-by-bd-init-prefix-bd-bootstrap-or-bd","errorCode":null,"errorMessage":"%q is set by bd init --prefix, bd bootstrap or bd rename-prefix, not by a config write: storing it here would leave existing ids under the old prefix with nothing to reconcile them","messagePattern":"%q is set by bd init --prefix, bd bootstrap or bd rename-prefix, not by a config write: storing it here would leave existing ids under the old prefix with nothing to reconcile them","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workapi/workspaceconfig.go","lineNumber":51,"sourceCode":"\t\treturn \"\", fmt.Errorf(\"%w: config key must not be empty\", issueops.ErrValidation)\n\t}\n\treturn key, nil\n}\n\n// ValidateSettingWrite checks a key and value a caller wants to STORE, and\n// returns the value as it will be stored, so that a body cannot store a\n// different string from the one that was checked.\nfunc ValidateSettingWrite(key, value string) (string, error) {\n\tif _, err := ValidateSettingKey(key); err != nil {\n\t\treturn \"\", err\n\t}\n\t// The prefix is owned by bd init --prefix, bd bootstrap and bd\n\t// rename-prefix. Refused HERE rather than at the front door because `bd\n\t// config set` is not the only door that reaches this plane: before this\n\t// role existed `bd config set-many issue_prefix=x` walked past the guard\n\t// and re-prefixed the workspace.\n\tif key == issueops.SettingKeyIssuePrefix || key == \"issue-prefix\" {\n\t\treturn \"\", fmt.Errorf(\"%w: %q is set by bd init --prefix, bd bootstrap or bd rename-prefix, not by a config write: \"+\n\t\t\t\"storing it here would leave existing ids under the old prefix with nothing to reconcile them\",\n\t\t\tissueops.ErrValidation, key)\n\t}\n\t// status.custom is PROJECTED into custom_statuses, which reads consult\n\t// first, so a value that cannot be projected must not become a row.\n\t// Checking here rather than leaving it to SyncCustomStatusesTable is what\n\t// makes the refusal a validation error rather than a storage failure.\n\tif key == issueops.SettingKeyStatusCustom && value != \"\" {\n\t\tif _, err := types.ParseCustomStatusConfig(value); err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"%w: invalid %s value: %v\", issueops.ErrValidation, key, err)\n\t\t}\n\t}\n\treturn value, nil\n}\n\n// FilterSettingsEnumeration takes the rows a store handed back and returns the\n// ones the settings enumeration is allowed to carry: everything except the KV\n// plane.","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/workspaceconfig.go#L33-L69","documentation":"ValidateSettingWrite refuses to store the issue-prefix setting under either spelling (issue_prefix or issue-prefix), wrapping issueops.ErrValidation. The prefix is owned exclusively by bd init --prefix, bd bootstrap, and bd rename-prefix; storing it via a plain config write would leave existing IDs under the old prefix with nothing to reconcile them. The guard sits here rather than at the CLI front door because config set-many previously bypassed it.","triggerScenarios":"Calling ValidateSettingWrite(\"issue_prefix\", x) or ValidateSettingWrite(\"issue-prefix\", x), e.g. via bd config set issue_prefix=NEW or bd config set-many issue_prefix=x.","commonSituations":"Trying to rename a workspace prefix by hand through config instead of bd rename-prefix; copy-pasted setup scripts from before the dedicated commands existed; automation that writes all settings wholesale including the prefix.","solutions":["Use bd rename-prefix to change the prefix so existing IDs are reconciled","For new workspaces use bd init --prefix or bd bootstrap instead of a config write","Remove issue_prefix/issue-prefix from any config set/set-many scripts and write only non-protected keys"],"exampleFix":"// before\nbd config set issue_prefix=NEW\n// after\nbd rename-prefix NEW","handlingStrategy":"validation","validationCode":"var protectedKeys = map[string]bool{\"issue_prefix\": true, \"issue-prefix\": true}\nif protectedKeys[key] { return fmt.Errorf(\"use bd rename-prefix to change %s\", key) }","typeGuard":"func isPrefixKey(k string) bool { return k == \"issue_prefix\" || k == \"issue-prefix\" }","tryCatchPattern":"_, err := workapi.ValidateSettingWrite(key, val)\nif errors.Is(err, issueops.ErrValidation) && isPrefixKey(key) { /* route to bd rename-prefix flow */ }","preventionTips":["Treat issue_prefix/issue-prefix as reserved and filter them out of bulk config writes","Use bd rename-prefix, bd init --prefix, or bd bootstrap for all prefix changes","Audit automation scripts for hardcoded prefix config writes"],"tags":["config","validation","prefix"],"backgroundTag":"protected-config-key","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}