gastownhall/beads · error

metadata.repo must not be null

Error message

metadata.repo must not be null

What it means

After unmarshaling, githubRepoFromIssue checks the decoded `repo` value; if it decodes to JSON null (rather than being absent), the function rejects it with "metadata.repo must not be null". A null repo is indistinguishable from corruption, so it fails loudly instead of silently skipping gate checks.

Source

Thrown at cmd/bd/gate.go:874

	if issue == nil || len(issue.Metadata) == 0 || string(issue.Metadata) == "null" {
		return "", nil
	}

	var raw map[string]json.RawMessage
	if err := json.Unmarshal(issue.Metadata, &raw); err != nil {
		return "", fmt.Errorf("metadata must be a JSON object: %w", err)
	}
	repoRaw, hasRepo := raw["repo"]
	if !hasRepo {
		return "", nil
	}

	var repoValue interface{}
	if err := json.Unmarshal(repoRaw, &repoValue); err != nil {
		return "", fmt.Errorf("metadata.repo: %w", err)
	}
	if repoValue == nil {
		return "", fmt.Errorf("metadata.repo must not be null")
	}
	repo, ok := repoValue.(string)
	if !ok {
		return "", fmt.Errorf("metadata.repo must be a string, got %T", repoValue)
	}
	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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Delete the `repo` key entirely rather than setting it to null — absence means "no repo configured" and returns empty string gracefully.
  2. Set a valid value: `"repo": "owner/repo"` or `"host/owner/repo"` in the issue metadata.
  3. Update whatever writer (script or tool) nulls the field so it removes the key instead.

Example fix

// before
{"repo": null}
// after
{} // or {"repo": "gastownhall/beads"}
Defensive patterns

Strategy: validation

Validate before calling

var v interface{}
if err := json.Unmarshal(md["repo"], &v); err == nil && v == nil {
    delete(md, "repo") // treat null as "no repo" instead of leaving null
}

Type guard

func repoMetadataIsPresent(md map[string][]byte) bool {
    raw, ok := md["repo"]
    if !ok { return false }
    var v interface{}
    if err := json.Unmarshal(raw, &v); err != nil { return false }
    return v != nil
}

Prevention

When it happens

Trigger: The issue metadata stores `"repo": null` explicitly. json.Unmarshal succeeds and yields repoValue == nil, hitting this branch during repoMetadataForGate or any GH run/PR gate check.

Common situations: Automation that clears fields by setting them to null instead of deleting the key; partial JSON merges that zeroed the value; hand-edited issue exports.

Related errors


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