{"record":{"id":"70676aa372ee8443","repo":"gastownhall/beads","slug":"parse-gh-output-w-70676a","errorCode":null,"errorMessage":"parse gh output: %w","messagePattern":"parse gh output: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/gate_discover.go","lineNumber":475,"sourceCode":"\t}\n\tif repo != \"\" {\n\t\targs = append(args, \"--repo\", repo)\n\t}\n\tif workflow != \"\" {\n\t\targs = append(args, \"--workflow\", workflow)\n\t}\n\n\toutput, stderr, err := runGH(args...)\n\tif err != nil {\n\t\tif len(stderr) > 0 {\n\t\t\treturn nil, fmt.Errorf(\"gh run list failed: %s\", string(stderr))\n\t\t}\n\t\treturn nil, fmt.Errorf(\"gh run list: %w\", err)\n\t}\n\n\tvar runs []GHWorkflowRun\n\tif err := json.Unmarshal(output, &runs); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse gh output: %w\", err)\n\t}\n\n\treturn runs, nil\n}\n\n// matchGateToRun finds the best matching run for a gate using heuristics.\n// If the gate has a workflow name hint in AwaitID, only runs matching that workflow are considered.\n//\n// foreignRepo must be true when runs were queried from a repo other than the\n// current one (SF1: a gate whose metadata.repo targets another repository).\n// In that case the local commit SHA and branch name are meaningless - they\n// describe the current checkout, not the foreign repo - so the commit/branch\n// heuristics are skipped entirely rather than comparing against them anyway.\nfunc matchGateToRun(gate *types.Issue, runs []GHWorkflowRun, maxAge time.Duration, foreignRepo bool) *GHWorkflowRun {\n\tworkflowHint := getWorkflowNameHint(gate)\n\n\t// Cross-repo discovery requires a workflow hint. With the commit/branch\n\t// heuristics below neutralized for a foreign repo, a hintless gate could","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/gate_discover.go#L457-L493","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run `gh run list --json ...` manually with the same flags and inspect the raw output for non-JSON text","Run `gh auth status` / `gh auth login` to fix authentication, the most common cause of non-JSON output","Upgrade gh CLI (`gh --version`) so all requested --json fields are supported","Check for shell wrappers/aliases or proxies mangling gh stdout"],"exampleFix":"// before: assumes gh always outputs valid JSON\nout, err := runGh(ctx, \"gh\", \"run\", \"list\", args...)\nif err != nil { return nil, fmt.Errorf(\"gh run list: %w\", err) }\nvar runs []GHWorkflowRun\nif err := json.Unmarshal(output, &runs); err != nil {\n    return nil, fmt.Errorf(\"parse gh output: %w\", err)\n}\n// after: fail fast with gh's raw output when it is not JSON\nif !json.Valid(bytes.TrimSpace(output)) {\n    return nil, fmt.Errorf(\"gh run list: non-JSON output: %s\", string(output))\n}\nvar runs []GHWorkflowRun\nif err := json.Unmarshal(output, &runs); err != nil {\n    return nil, fmt.Errorf(\"parse gh output: %w\", err)\n}","handlingStrategy":"validation","validationCode":"raw, _ := exec.Command(\"gh\", \"auth\", \"status\").CombinedOutput()\nif !strings.Contains(string(raw), \"Logged in\") {\n    return errors.New(\"gh CLI not authenticated; run gh auth login before gate discover\")\n}","typeGuard":"func isJSONRunList(output []byte) bool {\n    var runs []GHWorkflowRun\n    return json.Unmarshal(bytes.TrimSpace(output), &runs) == nil && runs != nil\n}","tryCatchPattern":"runs, err := queryGitHubRunsInRepo(ctx, dir)\nif err != nil {\n    var parseErr *json.UnmarshalTypeError\n    if errors.As(err, &parseErr) {\n        return fmt.Errorf(\"gh output schema mismatch at %v; check gh version and --json fields\", parseErr.Struct)\n    }\n    return err\n}","preventionTips":["Verify `gh auth status` succeeds before running gate discovery","Pin a minimum gh CLI version that supports all --json fields the command requests","Test gate discover with `gh run list --json ...` manually to see raw output on failure","Avoid shell wrappers or pipes that prepend non-JSON text to gh stdout"],"tags":["cli","json","github-actions","parsing"],"backgroundTag":"gh-output-json-parse-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}