{"record":{"id":"042699b3ec244649","repo":"gastownhall/beads","slug":"invalid-s-value-v","errorCode":null,"errorMessage":"invalid %s value: %v","messagePattern":"invalid (.+?) value: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/workapi/workspaceconfig.go","lineNumber":61,"sourceCode":"\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.\n//\n// THE KV PLANE RIDES IN THE SAME TABLE AND IS NOT SETTINGS. Generic `bd kv`\n// keys and the `bd remember` memories nested under them are USER DATA stored as\n// config rows beneath kvkeys.Prefix, and an enumeration that returned them\n// published that data on `bd config list` and on GET /v0/beads/config alike —\n// the latter reachable by anything a shared bearer admits, if one is\n// configured at all, and redacting on the KEY NAME while a memory's content is\n// in the VALUE. `bd config list` carrying kv rows was never a\n// design; it fell out of one storage table holding two planes.\n//","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/workspaceconfig.go#L43-L79","documentation":"ValidateSettingWrite validates status.custom values by parsing them with types.ParseCustomStatusConfig before the value can be stored, wrapping issueops.ErrValidation. status.custom is projected into the custom_statuses table that readers consult first, so a value that cannot be projected must never become a row. Checking here makes the refusal a clean validation error rather than a later storage failure.","triggerScenarios":"Calling ValidateSettingWrite(\"status.custom\", <malformed value>) with a non-empty value that ParseCustomStatusConfig rejects, e.g. bd config set status.custom='not-json' or a structurally invalid custom-status config.","commonSituations":"Hand-editing status.custom values with typos or wrong JSON structure; migrating configs between schema versions where the custom-status format changed; shell quoting mangling the JSON before it reaches the API.","solutions":["Validate the value locally with types.ParseCustomStatusConfig(value) and fix the structure before storing","Use the canonical JSON shape for custom statuses (check the parser for required fields)","Prefer dedicated commands for managing custom statuses rather than raw config writes where available"],"exampleFix":"// before\nbd config set status.custom='{\"label\":\"in-progress\"}'\n// after\nbd config set status.custom='{\"label\":\"in-progress\",\"count\":null,\"order\":0}'","handlingStrategy":"validation","validationCode":"if _, err := types.ParseCustomStatusConfig(value); err != nil {\n    return fmt.Errorf(\"status.custom is not valid: %v\", err)\n}","typeGuard":"func validCustomStatus(v string) bool { _, err := types.ParseCustomStatusConfig(v); return err == nil }","tryCatchPattern":"_, err := workapi.ValidateSettingWrite(\"status.custom\", val)\nif errors.Is(err, issueops.ErrValidation) { /* show parse error, do not retry as-is */ }","preventionTips":["Pre-validate status.custom with types.ParseCustomStatusConfig before storing","Build custom-status JSON programmatically instead of hand-writing it","Beware shell quoting mangling JSON; use single quotes or heredocs","Keep custom-status configs aligned with the current schema when upgrading"],"tags":["config","validation","custom-status"],"backgroundTag":"schema-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}