gastownhall/beads · error
repo %q contains invalid character %q
Error message
repo %q contains invalid character %q
What it means
GitHub repository/owner names are limited to alphanumerics, hyphens, underscores, and dots. githubRepoFromIssue enforces this per component and reports the exact offending character and the full repo string, preventing invalid values from reaching `gh run list --repo`.
Source
Thrown at cmd/bd/gate.go:897
}
if repo == "" {
return "", nil
}
parts := strings.Split(repo, "/")
if len(parts) != 2 && len(parts) != 3 {
return "", fmt.Errorf("repo %q must use OWNER/REPO or HOST/OWNER/REPO", repo)
}
for _, part := range parts {
if part == "" {
return "", fmt.Errorf("repo %q contains an empty path component", repo)
}
for _, char := range part {
if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
(char >= '0' && char <= '9') || char == '-' || char == '_' || char == '.' {
continue
}
return "", fmt.Errorf("repo %q contains invalid character %q", repo, char)
}
}
return repo, nil
}
// isGitHubGateType returns true for gate types whose condition is checked
// against a GitHub repository (gh:run, gh:pr, and any future gh:* type).
func isGitHubGateType(gateType string) bool {
return strings.HasPrefix(gateType, "gh:")
}
// repoMetadataForGate computes the metadata to store on a new ad-hoc gate,
// inheriting a validated GitHub repo selector from the blocked issue.
//
// This is restricted to gh:* gate types (SF4): "repo" is legal, unrelated
// metadata on any issue (the metadata contract allows arbitrary JSON), so
// running GitHub-repo validation for human/timer gates would fail ordinaryView on GitHub (pinned to 71377f2769)
Solutions
- Remove/replace invalid characters so each component matches [A-Za-z0-9._-], e.g. "gastownhall/beads".
- Strip URL encoding or query fragments before storing: use the decoded path without scheme/parameters.
- Quote values properly in shell scripts to avoid stray characters being captured into metadata.
- Re-run `bd` gate checks after fixing to confirm the repo resolves.
Example fix
// before
{"repo": "gastownhall / beads"}
// after
{"repo": "gastownhall/beads"} Defensive patterns
Strategy: validation
Validate before calling
func validRepoChars(repo string) bool {
for _, part := range strings.Split(repo, "/") {
for _, c := range part {
ok := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.'
if !ok { return false }
}
}
return true
} Type guard
var repoComponentRE = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
func charsAllowed(s string) bool {
for _, p := range strings.Split(s, "/") {
if !repoComponentRE.MatchString(p) { return false }
}
return true
} Prevention
- Validate repo strings with a regex at the point of input, not at gate-check time.
- Reject pasted URLs early; extract just the owner/repo path.
- Use url.PathUnescape only if input may be URL-encoded, then re-validate characters.
When it happens
Trigger: metadata.repo contains characters outside [A-Za-z0-9._-], e.g. spaces, "owner~repo", a URL-encoded segment like "%20", or non-ASCII characters; hit during any gate check that resolves the repo.
Common situations: Pasting "owner / repo" with spaces; including URL escapes or query strings; Unicode lookalikes from copy-paste; shell interpolation leaking separators like ":" or "@".
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- repo %q must use OWNER/REPO or HOST/OWNER/REPO
- repo %q contains an empty path component
- identity: invalid proxy secret
- backend must be set
- invalid repo metadata: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/aaa65d9aa4b32ecb.
Report an issue: GitHub.