---
name: gh-run-poll-jobs-not-status
description: "gh run view --json status reads \"queued\" with frozen updatedAt while jobs actually run — poll --json jobs; gh emits \"\" not null so .conclusion // .status renders blank."
metadata: 
  node_type: memory
  type: feedback
  originSessionId: d780a4d4-c7b1-44a2-818b-9d5f41a0eb4e
  modified: 2026-08-02T17:07:31.277Z
---

`gh run view <id> --json status` returned `queued` with `updatedAt` frozen while both `test` jobs were `in_progress` (run 30509109149, 2026-07-30). doyle nearly opened a false "runner deaf" incident off it. Also: gh emits `""` not `null` for pending conclusions, so `.conclusion // .status` jq renders blank.

**Why:** the run-level status field lags/lies under queued n1-gate jobs; job-level state is the truth.

**How to apply:** poll `gh run view <id> --json jobs`, never `--json status`. When gating on completion, require every job `completed success` and expected jobs PRESENT by name (not skipped, not absent). See [[v0460-published]].

**Reading a red while the run is still going (2026-08-02, golden 30754060525):** `gh run view --log-failed` REFUSES with "run is still in progress; logs will be available when it is complete" — but `gh api repos/<o>/<r>/actions/jobs/<job_id>/logs` serves a COMPLETED job's log immediately, and job ids come from the same `--json jobs` call. A run still running is not a run you cannot read.

**Two traps in the log itself:** grepping for `FAIL|error` matches TEST NAMES containing "failed" (`failedaddr::tests::…`, `stage_then_swap_failed_fetch_preserves_install`) and reports carnage while every suite is green — read the `##[group]Run` step boundaries and the `##[error]` line to find which STEP died. A `test` job can red with 2584/2584 passing because the failure is a later step (clippy, or a link step that cannot replace `target\debug\spt.exe`). See [[live-agents-lock-target-debug-spt-exe]].

**The JOB SET is not fixed, so "all jobs complete" false-finishes (2026-08-20, golden 32341702157/32348817055).** The entry above says gate on every job completed *and* expected jobs present by name — this is the mechanism that makes the second clause load-bearing. Jobs are CREATED as the run schedules them: my first sample of a golden showed TWO jobs (`traceability=success changes=queued`), the run settled at EIGHT, and it finished at NINE because `notify` is created last. A predicate of "no job has `status != completed`" would have reported the run FINISHED twice over — once at the two-job stage, once before `notify` existed — and both times it would have read as a clean, confident verdict on a run that had barely started. Absence of a pending job is not presence of a finished run.

**How to apply:** take completion from the RUN OBJECT — `gh api repos/<o>/<r>/actions/runs/<id>` → `.status == "completed"`, with `.conclusion` for the verdict — and use `--json jobs` for the CONTENT (which job died, its id for the raw log). The freeze in the headline of this entry was `gh run view --json status`, the CLI's cached field; the API run object is a different read and did not lag across two full golden runs. Display the job map, gate on the run.
