gastownhall/beads · error

metadata.repo must be a string, got %T

Error message

metadata.repo must be a string, got %T

What it means

githubRepoFromIssue requires the decoded `repo` metadata value to be a JSON string. If it is any other JSON type (number, bool, array, object), the type assertion to string fails and the function errors with the Go type of the offending value (e.g. got float64, got []interface {}).

Source

Thrown at cmd/bd/gate.go:878

	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 {
			if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
				(char >= '0' && char <= '9') || char == '-' || char == '_' || char == '.' {
				continue
			}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Correct the stored value to a string: `"repo": "owner/repo"` or `"host/owner/repo"`.
  2. If a non-string was intentional (e.g. an ID), move it to a different metadata key and keep `repo` string-only.
  3. Fix the writer producing metadata so it serializes repo as a quoted JSON string.
  4. Re-run the gate check (`bd doctor` gate flow) after fixing metadata to confirm resolution succeeds.

Example fix

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

Strategy: type-guard

Validate before calling

var v interface{}
if err := json.Unmarshal(md["repo"], &v); err == nil {
    if _, isStr := v.(string); !isStr {
        return errors.New("metadata.repo must be a JSON string")
    }
}

Type guard

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

Prevention

When it happens

Trigger: Issue metadata contains e.g. `"repo": 123`, `"repo": true`, or `"repo": ["owner","repo"]`; githubRepoFromIssue is called during repoMetadataForGate or GH gate checks and the string type assertion `repoValue.(string)` fails.

Common situations: Scripts writing YAML/JSON booleans or numbers into metadata; double-encoding bugs where a value was unquoted; bulk-import tooling mapping fields incorrectly.

Related errors


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