{"record":{"id":"64e2f02a004be8d7","repo":"gastownhall/beads","slug":"metadata-repo-must-be-a-string-got-t","errorCode":null,"errorMessage":"metadata.repo must be a string, got %T","messagePattern":"metadata\\.repo must be a string, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/gate.go","lineNumber":878,"sourceCode":"\tvar raw map[string]json.RawMessage\n\tif err := json.Unmarshal(issue.Metadata, &raw); err != nil {\n\t\treturn \"\", fmt.Errorf(\"metadata must be a JSON object: %w\", err)\n\t}\n\trepoRaw, hasRepo := raw[\"repo\"]\n\tif !hasRepo {\n\t\treturn \"\", nil\n\t}\n\n\tvar repoValue interface{}\n\tif err := json.Unmarshal(repoRaw, &repoValue); err != nil {\n\t\treturn \"\", fmt.Errorf(\"metadata.repo: %w\", err)\n\t}\n\tif repoValue == nil {\n\t\treturn \"\", fmt.Errorf(\"metadata.repo must not be null\")\n\t}\n\trepo, ok := repoValue.(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"metadata.repo must be a string, got %T\", repoValue)\n\t}\n\tif repo == \"\" {\n\t\treturn \"\", nil\n\t}\n\n\tparts := strings.Split(repo, \"/\")\n\tif len(parts) != 2 && len(parts) != 3 {\n\t\treturn \"\", fmt.Errorf(\"repo %q must use OWNER/REPO or HOST/OWNER/REPO\", repo)\n\t}\n\tfor _, part := range parts {\n\t\tif part == \"\" {\n\t\t\treturn \"\", fmt.Errorf(\"repo %q contains an empty path component\", repo)\n\t\t}\n\t\tfor _, char := range part {\n\t\t\tif (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||\n\t\t\t\t(char >= '0' && char <= '9') || char == '-' || char == '_' || char == '.' {\n\t\t\t\tcontinue\n\t\t\t}","sourceCodeStart":860,"sourceCodeEnd":896,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/gate.go#L860-L896","documentation":"githubRepoFromIssue requires the decoded `repo` metadata value to be a JSON string. If it is any other JSON type (number, bool, array, object), the type assertion to string fails and the function errors with the Go type of the offending value (e.g. got float64, got []interface {}).","triggerScenarios":"Issue metadata contains e.g. `\"repo\": 123`, `\"repo\": true`, or `\"repo\": [\"owner\",\"repo\"]`; githubRepoFromIssue is called during repoMetadataForGate or GH gate checks and the string type assertion `repoValue.(string)` fails.","commonSituations":"Scripts writing YAML/JSON booleans or numbers into metadata; double-encoding bugs where a value was unquoted; bulk-import tooling mapping fields incorrectly.","solutions":["Correct the stored value to a string: `\"repo\": \"owner/repo\"` or `\"host/owner/repo\"`.","If a non-string was intentional (e.g. an ID), move it to a different metadata key and keep `repo` string-only.","Fix the writer producing metadata so it serializes repo as a quoted JSON string.","Re-run the gate check (`bd doctor` gate flow) after fixing metadata to confirm resolution succeeds."],"exampleFix":"// before\n{\"repo\": 12345}\n// after\n{\"repo\": \"gastownhall/beads\"}","handlingStrategy":"type-guard","validationCode":"var v interface{}\nif err := json.Unmarshal(md[\"repo\"], &v); err == nil {\n    if _, isStr := v.(string); !isStr {\n        return errors.New(\"metadata.repo must be a JSON string\")\n    }\n}","typeGuard":"func repoIsString(md map[string][]byte) (string, bool) {\n    var v interface{}\n    if json.Unmarshal(md[\"repo\"], &v) != nil { return \"\", false }\n    s, ok := v.(string)\n    return s, ok\n}","tryCatchPattern":null,"preventionTips":["Always serialize repo as a quoted string when writing metadata.","Add a schema check that validates metadata value types on issue creation/update.","Watch for double-encoding: unmarshal-then-remarshal when copying metadata between systems."],"tags":["json","metadata","type-mismatch"],"backgroundTag":"metadata-type-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}