gastownhall/beads · error
invalid repo metadata: %w
Error message
invalid repo metadata: %w
What it means
`bd gate discover` validates each gate's metadata.repo via githubRepoFromIssue before using it as a GitHub repo selector. When the metadata contains a malformed repo value (explicit "repo":null, a non-string repo value, or a repo string not matching [HOST/]OWNER/REPO), the function returns an error and matchGatesToRuns wraps it as "invalid repo metadata: %w" instead of silently falling back to the current repository, since a silent fallback could point a cross-repo check at the wrong repo.
Source
Thrown at cmd/bd/gate_discover.go:272
// repo's runs of a same-named workflow, which would otherwise persist the
// wrong await_id permanently (the persisted ID pins the gate).
//
// queryRuns receives a workflowHint - the gate's AwaitID workflow name hint,
// non-empty only for a foreign (cross-repo) query - so it can narrow the
// `gh run list` call with --workflow. Without that narrowing, a busy foreign
// repo's unfiltered recent-run list (capped by --limit) might never surface
// the specific workflow a gate is waiting on. The current repo's query is
// never narrowed this way (workflowHint is always "" for it), matching
// pre-existing `bd gate discover` behavior for local gates.
func matchGatesToRuns(gates []*types.Issue, maxAge time.Duration, queryRuns func(repo, workflowHint string) ([]GHWorkflowRun, error)) []gateDiscoveryMatch {
runsByKey := make(map[string][]GHWorkflowRun)
queryErrByKey := make(map[string]error)
results := make([]gateDiscoveryMatch, 0, len(gates))
for _, gate := range gates {
repo, repoErr := githubRepoFromIssue(gate)
if repoErr != nil {
results = append(results, gateDiscoveryMatch{gate: gate, err: fmt.Errorf("invalid repo metadata: %w", repoErr)})
continue
}
foreign := repo != ""
hint := getWorkflowNameHint(gate)
// Cross-repo discovery requires a workflow hint. With local-commit/
// local-branch heuristics neutralized for a foreign repo (see
// matchGateToRun), a hintless gate could only ever score on time
// proximity alone and risk pinning the wrong run in another
// repository permanently. Skip the query entirely rather than spend
// a GitHub API call on a gate that can never match.
if foreign && hint == "" {
results = append(results, gateDiscoveryMatch{gate: gate})
continue
}
queryHint := ""View on GitHub (pinned to 71377f2769)
Solutions
- Run `bd show <gate-id>` and inspect the gate's metadata.repo value
- Fix the metadata with `bd update <gate-id> --metadata '{"repo":"OWNER/REPO"}'` (valid OWNER/REPO or HOST/OWNER/REPO string)
- If the gate should target the current repo, remove the repo key from metadata entirely rather than setting it to null
- Re-run `bd gate discover`; the remaining gates will be processed
Example fix
// before (invalid metadata on gate)
{"repo": null}
// after
{"repo": "gastownhall/beads"} Defensive patterns
Strategy: validation
Validate before calling
func validRepoMetadata(md json.RawMessage) bool {
if len(md) == 0 || string(md) == "null" { return true } // current repo
var m map[string]json.RawMessage
if json.Unmarshal(md, &m) != nil { return false }
raw, ok := m["repo"]
if !ok { return true }
var s string
if json.Unmarshal(raw, &s) != nil { return false } // null or non-string
parts := strings.Split(s, "/")
return len(parts) == 2 || len(parts) == 3 // OWNER/REPO or HOST/OWNER/REPO
} Type guard
func gateRepoIsWellFormed(g *types.Issue) bool {
_, err := githubRepoFromIssue(g)
return err == nil
} Prevention
- Never set metadata.repo to JSON null; omit the key to mean 'current repo'
- Only write repo values as OWNER/REPO or HOST/OWNER/REPO strings, not URLs
- Create gates with `bd gate create` so repo metadata is validated at write time
- Audit existing gates with a script that calls bd list --json and re-validates metadata.repo before running discovery
When it happens
Trigger: Running `bd gate discover` when a gate issue's metadata.repo is invalid: the key exists but holds JSON null, a non-string value (number/bool/object), or a string that fails [HOST/]OWNER/REPO validation (e.g. "owneronly", "a/b/c/d", empty string).
Common situations: Hand-edited or script-written issue metadata with a typo'd repo field; an old gate created before validation was added; metadata written by another tool using a different repo format (URL instead of OWNER/REPO); JSON null stored explicitly instead of omitting the key.
Related errors
- ExternalDoltConfig: must set Socket or (Host, Port)
- metadata must be a JSON object: %w
- metadata.repo: %w
- metadata.repo must not be null
- repo %q must use OWNER/REPO or HOST/OWNER/REPO
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/46619dc77bd44905.
Report an issue: GitHub.