| name | gh-hound |
| description | Use gh-hound's structured GitHub Actions CI surface for agent workflows: inspect runs, detect failures, watch fail-fast, parse JSON failure triage (job, step, exit_code, log_excerpt), and branch on gh-hound exit codes instead of screen-scraping the TUI. |
gh-hound
Use when inspecting GitHub Actions CI with gh hound — fix loops, PR checks, post-push verification. Always use the pipe surface; never screen-scrape the TUI or parse raw GitHub logs when failed[] is available.
Commands
gh hound runs --no-tui --json
gh hound runs --status failure --no-tui --json
gh hound runs --all --no-tui --json
gh hound runs --branch <ref> --no-tui --json
gh hound runs -R owner/repo --no-tui --json
gh hound watch --json
gh hound watch --group --no-tui
gh hound watch --group --no-tui --timeout 10m
gh hound queue --run <run-id> --no-tui --json
gh hound queue --job <job-id> --no-tui --json
gh hound queue --group <name> --no-tui --json
gh hound queue --groups --no-tui --json
gh hound runs --run <id> --attempt 2 --no-tui --json
gh hound artifacts --no-tui --json
gh hound artifacts --run <id> --download <name> --dir <path> --no-tui --json
gh hound approvals --run <id> --no-tui --json
gh hound approvals --run <id> --approve --env production --comment "lgtm" --no-tui --json
gh hound diff --workflow CI --no-tui --json
gh hound flakes --workflow CI --no-tui --json
gh hound caches --no-tui --json
gh hound caches --delete-key <key> --ref <ref> --no-tui --json
gh hound workflows --no-tui --json
gh hound workflows --enable <id|path> --no-tui --json
Runs are scoped to the current git branch by default. An empty branch-scoped runs[] can be a target mismatch, especially for release, tag-push, or branchless workflows. If the top-level diagnostic.kind is target_mismatch, follow diagnostic.next_commands[]: widen with --all, pass --branch <ref>, inspect --run <id>, or use watch -R owner/repo --group --run <id> --timeout 10m for a bounded await. Do not conclude CI is absent until you have tried one of those target-correcting commands.
Exit Codes
0: all selected runs green; continue.
1: action needed; inspect runs[].failed[] and fix.
2: API/network/config error; retry or report infrastructure failure, not CI failure.
3: pending/running; wait and re-poll, or use watch.
watch --json is fail-fast: it exits 1 the moment the watched run turns red and includes the failure payload immediately.
watch --group --no-tui is the blocking/await mode: it streams the selected run's whole event group (same head_sha + event) as NDJSON — one {type:"run", ts, run_id, workflow, status, conclusion} line per state transition, closed by a {type:"summary", …, running, home, lost, timed_out} object — and blocks until the hunt settles. Exit 1 if any run is lost, 0 when the hunt comes home. (Plain watch --json without --group only snapshots the active run and exits 3 while pending — use --group to block.) Events are run-level only — drill into a single run with watch --json for job/step detail. Rehearse with --fake-scenario pack.
For unattended/background agents, bound the block with --timeout <duration> (e.g. --timeout 10m) so a queued or stuck run — or a hung GitHub call (the deadline bounds an in-flight poll too) — can't hang the watcher forever. On expiry the stream closes with a {…, "timed_out":true} summary (still carrying live running members). Exit follows the same worst-outcome rule as a clean settle: 1 if any member is already lost, else 3 (still in flight). The timed_out marker — not the exit code — is what tells a cut-short hunt from a settled one, so branch on the code as usual and read timed_out to know whether to re-poll. Without --timeout the block is unbounded (unchanged). --timeout requires --group; a negative duration is rejected.
Why Is It Pending? (queue)
If watch exits 3 or a run is pending, ask hound whether GitHub exposes a concurrency-group blocker before rerunning:
gh hound queue --run <run-id> --no-tui --json
gh hound queue --job <job-id> --no-tui --json
gh hound queue --groups --no-tui --json
Branch on decision.action:
wait (exit 3): a named concurrency group has live members ahead; do not rerun blindly.
inspect_gates_or_runner (exit 0): this pending state is not exposed as concurrency; check approvals, runner capacity, or normal watch.
normal_watch / list_only / unsupported (exit 0): informational or fallback path.
error (exit 2): validation/API/network/auth/render failure.
Rehearse with --fake-scenario queue --run 30433644 (blocked behind deploy-prod, exit 3).
Every queue envelope includes fetched_at. Treat --groups as a summary surface: it can include last_acquired_at, but absent active_member_count means hound did not fetch group members, not that the active group is empty.
JSON Shape
Top level: repo, branch, runs[], optional diagnostic. Each run: id, workflow, run_number, event, head_branch, head_sha, status, conclusion, created_at, html_url, failed[].
diagnostic appears only for actionable empty listings. For target_mismatch, branch on kind, read the human message, and execute one of next_commands[] instead of silently stopping.
Each failed[] entry: job, step, exit_code, annotations[] (path, line, level, message), log_excerpt.
Artifacts: gh hound artifacts lists {id, name, size_in_bytes, expired, expires_at, digest} for a run (latest on branch when --run omitted); --download <name|id> extracts into <dir>/<artifact-name>/ and reports downloaded.path. Exit 0 success, 2 any error (expired artifacts are refused before download). Add --artifacts to runs for per-run artifact metadata (opt-in: paginated artifact-list calls per run, usually one).
Caches: gh hound caches reports usage (active_size_in_bytes, active_count, cap_bytes — the repo's configured storage limit, 10 GB by default; usage can exceed it because eviction lags) plus caches[] (id, key, ref, size_in_bytes, last_accessed_at, created_at). When CI suddenly slows, check the kennel: usage near cap_bytes means LRU eviction is thrashing your keys. Evict with --delete-id <id> or --delete-key <key> [--ref <ref>] (reports deleted.deleted_count). Exit 0 deleted or listed, 2 anything else with typed error.kind (not_found when nothing matched). The default runs path never touches the cache API.
Triage degrades per job: when a job log has expired, log_excerpt is empty and exit_code falls back to 1, but job, step, and annotations are always present for every failed job. An empty failed[] on a red run means job details could not be listed — fall back to html_url.
Regression Boundary (diff)
When a workflow is red and the question is which commits turned it, do not bisect — the answer is in run history:
gh hound diff --workflow CI --no-tui --json
Branch on status: located (exit 1) means a regression exists — last_good/first_bad are full run objects and suspect_commits[] ({sha, author, message}, capped at 50; total_suspects is the full count) is the blame range, with compare_url for humans. green and inconclusive both exit 0 — read verdict for the hound's one-liner (trail went cold after 1,000 runs. means the page budget ran out; raise HOUND_DIFF_MAX_PAGES). Exit 2 carries error: {kind, message}. Runs count by their latest attempt: a failure rerun to green is green. A sound loop: diff --json -> if located, inspect first_bad with runs --run <id> --no-tui --json -> fix or rerun.
Rehearse with --fake-scenario regression (deterministic boundary, exit 1).
Flaky or Real? (flakes)
Before burning time on a red run, ask whether the failure is a known squirrel:
gh hound flakes --workflow CI --no-tui --json
Branch on status (always the worst job verdict): flaky (exit 1) — the failure pattern is established, rerun --failed-only is the rational first move; suspect (exit 1) — one wobble on record, look at jobs[].evidence[] before deciding; clean (exit 0) — fresh scent, this failure is worth chasing as real; insufficient_data (exit 0) — under 5 signal runs and no evidence, treat as real. The decision recipe:
runs --json exits 1 -> flakes --workflow <w> --json.
status == "flaky" -> rerun --run <id> --failed-only --no-tui --json -> watch --json.
status == "clean" (or insufficient_data) -> investigate failed[] for real; do not rerun blindly.
status == "suspect" -> read evidence[] (attempt_flip beats cross_run_flap beats retry_mask) and decide.
Scoring is documented and stable: 0.45 per attempt flip + 0.30 per cross-run flap + 0.20 per retry mask, capped at 1.0; >= 0.6 is flaky, any evidence is suspect. sample_size/window/runs_scanned size the claim; signals_evaluated lists what was checked (retry masking is only detectable in runs that had a failed attempt — the call budget never blanket-downloads logs). Evidence comes from attempt job conclusions and logs, never annotations (the API only serves annotations for the latest attempt). An underfilled-but-flaky window still exits 1 — trust status, not sample_size.
Rehearse with --fake-scenario flaky (seeded flips + retry mask, exit 1).
Mutations
After diagnosing with runs --json, act without leaving the surface:
gh hound rerun --run <id> --failed-only --debug --no-tui --json
gh hound cancel --run <id> --no-tui --json
action in the result is one of rerun | rerun_failed | rerun_job | cancel | force_cancel. Exit 0 means accepted; 2 means it did not happen (read error). A sound agent loop: exit 1 from runs -> inspect failed[] -> if transient, rerun --failed-only --debug -> watch --json.
Deployment approvals: a waiting run is gated on environment review. approvals --run <id> lists the gates (exit 1 while any await review, 0 when none); --approve/--reject with no --env reviews everything you can approve, --env <name> (repeatable) targets gates, --comment is optional (a blank one sends reviewed from gh-hound — the API requires the field). Refusals are typed: unknown environment -> validation, not a required reviewer -> permission. Add --approvals to runs for pending_environments on waiting runs (opt-in, one call per waiting run).
Workflow State (workflows)
When a scheduled workflow silently stops, check its state before debugging YAML: GitHub disables crons after 60 days of repo inactivity (disabled_inactivity).
gh hound workflows --no-tui --json
gh hound workflows --enable ci.yml --no-tui --json
state is an open string; documented values are active, disabled_manually, disabled_inactivity, disabled_fork, deleted — branch on the ones you know, pass the rest through. Toggle by numeric id or workflow file path only (display names refuse as validation); only active ↔ disabled_manually/disabled_inactivity flips are valid. A toggle is exactly one API call and reports the landing state in toggled.state. Exit 0 ok, 2 refused with error: {kind, field?, message} — this verb never exits 1 or 3. A sound loop: empty runs[] on a branch with a schedule -> workflows --json -> if disabled_inactivity, --enable <path> -> re-poll runs.
Deterministic Scenarios
For testing agent behavior without live CI:
gh hound runs --no-tui --json --fake-scenario failure
gh hound runs --branch release/v1 --no-tui --json --fake-scenario empty
The JSON schema lives at internal/render/testdata/schema.json in the gh-hound repo; the mutation envelope is under $defs.mutation_result, the approvals envelope under $defs.approvals_result, the queue verdict under $defs.queue_result, the regression verdict under $defs.diff_result, the caches envelope under $defs.caches_result, the workflows envelope under $defs.workflows_result, the pack stream under $defs.watch_group_event / $defs.watch_group_summary, and the flake verdict under $defs.flakes_result.
Guardrails
- Never expose credentials, auth headers, or token-bearing URLs.
- Prefer
--json; use --format md or --format xml only for presentation/export.
- Treat exit code
1 as CI failure evidence, not a broken gh-hound invocation.