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

  1. Run `bd show <gate-id>` and inspect the gate's metadata.repo value
  2. Fix the metadata with `bd update <gate-id> --metadata '{"repo":"OWNER/REPO"}'` (valid OWNER/REPO or HOST/OWNER/REPO string)
  3. If the gate should target the current repo, remove the repo key from metadata entirely rather than setting it to null
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/46619dc77bd44905. Report an issue: GitHub.