gastownhall/beads · error

parse gh output: %w

Error message

parse gh output: %w

What it means

Wraps a json.Unmarshal failure when parsing stdout captured from `gh run list`. The library runs the gh CLI, expects a JSON array of GHWorkflowRun objects on stdout, and throws this when gh's output cannot be decoded into []GHWorkflowRun. It indicates gh produced unexpected non-JSON or schema-mismatched output rather than a JSON run list.

Source

Thrown at cmd/bd/gate_discover.go:475

	}
	if repo != "" {
		args = append(args, "--repo", repo)
	}
	if workflow != "" {
		args = append(args, "--workflow", workflow)
	}

	output, stderr, err := runGH(args...)
	if err != nil {
		if len(stderr) > 0 {
			return nil, fmt.Errorf("gh run list failed: %s", string(stderr))
		}
		return nil, fmt.Errorf("gh run list: %w", err)
	}

	var runs []GHWorkflowRun
	if err := json.Unmarshal(output, &runs); err != nil {
		return nil, fmt.Errorf("parse gh output: %w", err)
	}

	return runs, nil
}

// matchGateToRun finds the best matching run for a gate using heuristics.
// If the gate has a workflow name hint in AwaitID, only runs matching that workflow are considered.
//
// foreignRepo must be true when runs were queried from a repo other than the
// current one (SF1: a gate whose metadata.repo targets another repository).
// In that case the local commit SHA and branch name are meaningless - they
// describe the current checkout, not the foreign repo - so the commit/branch
// heuristics are skipped entirely rather than comparing against them anyway.
func matchGateToRun(gate *types.Issue, runs []GHWorkflowRun, maxAge time.Duration, foreignRepo bool) *GHWorkflowRun {
	workflowHint := getWorkflowNameHint(gate)

	// Cross-repo discovery requires a workflow hint. With the commit/branch
	// heuristics below neutralized for a foreign repo, a hintless gate could

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run `gh run list --json ...` manually with the same flags and inspect the raw output for non-JSON text
  2. Run `gh auth status` / `gh auth login` to fix authentication, the most common cause of non-JSON output
  3. Upgrade gh CLI (`gh --version`) so all requested --json fields are supported
  4. Check for shell wrappers/aliases or proxies mangling gh stdout

Example fix

// before: assumes gh always outputs valid JSON
out, err := runGh(ctx, "gh", "run", "list", args...)
if err != nil { return nil, fmt.Errorf("gh run list: %w", err) }
var runs []GHWorkflowRun
if err := json.Unmarshal(output, &runs); err != nil {
    return nil, fmt.Errorf("parse gh output: %w", err)
}
// after: fail fast with gh's raw output when it is not JSON
if !json.Valid(bytes.TrimSpace(output)) {
    return nil, fmt.Errorf("gh run list: non-JSON output: %s", string(output))
}
var runs []GHWorkflowRun
if err := json.Unmarshal(output, &runs); err != nil {
    return nil, fmt.Errorf("parse gh output: %w", err)
}
Defensive patterns

Strategy: validation

Validate before calling

raw, _ := exec.Command("gh", "auth", "status").CombinedOutput()
if !strings.Contains(string(raw), "Logged in") {
    return errors.New("gh CLI not authenticated; run gh auth login before gate discover")
}

Type guard

func isJSONRunList(output []byte) bool {
    var runs []GHWorkflowRun
    return json.Unmarshal(bytes.TrimSpace(output), &runs) == nil && runs != nil
}

Try / catch

runs, err := queryGitHubRunsInRepo(ctx, dir)
if err != nil {
    var parseErr *json.UnmarshalTypeError
    if errors.As(err, &parseErr) {
        return fmt.Errorf("gh output schema mismatch at %v; check gh version and --json fields", parseErr.Struct)
    }
    return err
}

Prevention

When it happens

Trigger: gh run list emits human-readable text (e.g. a login prompt, warning banner, or error message) instead of JSON; gh emits an error object or null where an array is expected; the --json field list passed by the command does not match the GHWorkflowRun struct fields; gh output includes trailing non-JSON lines (progress/auth notices) before the array.

Common situations: Developer is not authenticated with gh (gh prints an auth error to stdout/stderr), gh is an older version lacking a requested --json field, GH_PAGES/enterprise proxy injects a banner, or a partially-installed gh shim echoes shell text.

Related errors


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