{"record":{"id":"554c24aca32c4a3b","repo":"gastownhall/beads","slug":"repo-q-must-use-owner-repo-or-host-owner-repo","errorCode":null,"errorMessage":"repo %q must use OWNER/REPO or HOST/OWNER/REPO","messagePattern":"repo %q must use OWNER/REPO or HOST/OWNER/REPO","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/gate.go","lineNumber":886,"sourceCode":"\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}\n\t\t\treturn \"\", fmt.Errorf(\"repo %q contains invalid character %q\", repo, char)\n\t\t}\n\t}\n\n\treturn repo, nil\n}\n\n// isGitHubGateType returns true for gate types whose condition is checked","sourceCodeStart":868,"sourceCodeEnd":904,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/gate.go#L868-L904","documentation":"The `repo` string must be either \"OWNER/REPO\" (2 slash-separated parts) or \"HOST/OWNER/REPO\" (3 parts) so it can target gh CLI --repo lookups. Any other slash count (0, 1, 4+) makes the value unresolvable and triggers this error, including the original repo string in quotes.","triggerScenarios":"metadata.repo set to values like \"myrepo\", \"owner\" (no slash), or \"https://github.com/owner/repo\" (contains 3+ slashes after splitting on \"/\") while running gate checks that resolve the GitHub repo.","commonSituations":"Users pasting a full GitHub URL instead of the owner/repo path; typing just a repo name assuming a default owner; including a protocol or trailing slash in the value.","solutions":["Change the value to \"owner/repo\", e.g. \"gastownhall/beads\".","For a self-hosted GH instance use \"host/owner/repo\", e.g. \"github.example.com/acme/api\".","Strip URL schemes/host paths: convert \"https://github.com/owner/repo\" to \"owner/repo\" (or \"github.com/owner/repo\" for the 3-part form).","Re-run the gate check to confirm the repo resolves."],"exampleFix":"// before\n{\"repo\": \"https://github.com/gastownhall/beads\"}\n// after\n{\"repo\": \"gastownhall/beads\"}","handlingStrategy":"validation","validationCode":"func validRepoShape(repo string) bool {\n    parts := strings.Split(repo, \"/\")\n    return len(parts) == 2 || len(parts) == 3\n}\n// call before storing metadata: if !validRepoShape(repo) { ... }","typeGuard":"func isOwnerRepoOrHostOwnerRepo(s string) bool {\n    n := len(strings.Split(s, \"/\"))\n    return n == 2 || n == 3\n}","tryCatchPattern":null,"preventionTips":["Store owner/repo paths, never full URLs — strip scheme and domain first.","Document the accepted formats (OWNER/REPO, HOST/OWNER/REPO) wherever repo is configured.","Trim trailing slashes before persisting user input."],"tags":["validation","github","configuration"],"backgroundTag":"invalid-repo-format","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}