Skip to content

fix(ci): watch-ci.sh inspects only runs for the current head commit#3747

Merged
Yeraze merged 1 commit into
mainfrom
fix/watch-ci-filter-by-head-sha
Jun 25, 2026
Merged

fix(ci): watch-ci.sh inspects only runs for the current head commit#3747
Yeraze merged 1 commit into
mainfrom
fix/watch-ci-filter-by-head-sha

Conversation

@Yeraze

@Yeraze Yeraze commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Problem

scripts/watch-ci.sh polls gh run list --branch <branch>, which returns runs for every commit ever pushed to the branch. After a branch is fixed and re-pushed, the watcher kept surfacing the old commit's failed run indefinitely — reporting ✗ CI FAILED even though the new tip's runs all passed.

Observed live while monitoring PR #3733: the fix commit (077552dd) was green, but the watcher repeatedly reported the pre-fix commit's PR Tests failure, because that stale completed run outranked the still-queued runs on the new tip.

Fix

  • Resolve the target's current head SHA each poll cycle and filter the run list to that SHA (in jq — gh run list has no commit filter):
    • PR target → headRefOid (fork-safe).
    • Plain branch target → repos/{owner}/{repo}/commits/{branch} via the API.
  • Re-resolving every cycle means a push mid-watch is tracked automatically, and the existing "no runs yet → wait" branch now cleanly covers the window between a push and its runs being created.
  • On a transient SHA-resolution hiccup, fall back to the last known tip instead of crashing.
  • Fixed the failure hint, which printed the invalid gh run view --log-failed --branch <branch> (run view takes a run id, not --branch), and added the short SHA to the pass/fail summary lines.

Verification

  • bash -n clean.
  • Regression: watch-ci.sh -q 3733 (the case the old script wrongly failed) → ✓ CI PASSED — all checks green on claude/great-dijkstra-ueptaa @ 077552dd.
  • Branch path: watch-ci.sh main correctly resolves the tip and waits only on that commit's genuinely in-progress runs.

🤖 Generated with Claude Code

`gh run list --branch <branch>` returns runs for EVERY commit ever pushed to
the branch, so after a branch is fixed and re-pushed, watch-ci kept reporting
the OLD commit's failed run forever — even though the new tip's runs all
passed. This produced false "CI FAILED" verdicts (observed monitoring PR #3733,
where the fix commit was green but the watcher reported the pre-fix failure).

Resolve the target's current head SHA each poll cycle (PR → headRefOid,
fork-safe; plain branch → commits API) and filter the run list to that SHA in
jq. Re-resolving each cycle also means a push mid-watch is tracked
automatically, and "no runs for this SHA yet" cleanly covers the window between
a push and its runs being created. On a transient SHA-resolution failure, fall
back to the last known tip rather than crashing.

Also fix the failure hint, which suggested the invalid
`gh run view --log-failed --branch <branch>` (run view takes a run id, not
--branch), and surface the short SHA in the pass/fail summary lines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review

  • Read changed file
  • Analyze code quality, bugs, performance, security
  • Post review feedback

Summary

This is a well-targeted fix for a real operational problem. The script is clean, defensive, and well-commented. Below are a few observations:

Positive Observations

  • headRefOid vs headRefName for PRs — using headRefOid (the commit SHA) directly from the PR metadata is fork-safe and avoids a second API call. Good choice.
  • Fallback to LAST_SHA on transient API failures is a nice resilience touch.
  • Inline jq filter with headSha cleanly filters stale runs without adding a second gh call per cycle.
  • Fixed failure hint — the old gh run view --log-failed --branch <branch> was invalid syntax; the two-step suggestion is correct.

Issues / Suggestions

1. jq inline string interpolation (potential injection risk, line 118)

-q ".[] | select(.headSha == \"$HEAD_SHA\") | ..."

HEAD_SHA comes from GitHub API (headRefOid / .sha) and will always be a 40-char hex string, so in practice there's no injection risk. However, it's more idiomatic and safer to pass it as a --arg:

-q --arg sha "$HEAD_SHA" '.[] | select(.headSha == $sha) | "\(.name)|\(.status)|\(.conclusion)"'

This also avoids the nested quote escaping, improving readability.

2. Early exit on first failure may miss concurrent failures (line 162–169)

The "fail fast" logic exits as soon as FAILED_NAME is set, even if multiple workflows failed in the same cycle. This is by design (documented in the header), but worth noting: users only see the first failed workflow name, not all of them. The existing behaviour is acceptable for a "stop the watch" tool, but the failure message at line 164 could iterate all failures rather than just the first one.

3. LIMIT may be insufficient for large branches (line 117)

--limit 20 (default) combined with a busy branch could mean the current SHA's runs are beyond position 20 in the list. The filter is correct but silently returns empty results, which falls through to "No CI runs yet — waiting…". This could cause indefinite waiting. A brief comment noting this edge case (or bumping the default) would be helpful.

4. REPO variable unused when PR_NUMBER is set (lines 62–66)

The guard correctly exits only when PR_NUMBER is empty and REPO is empty, so the logic is sound. Minor: the comment on line 61 says "REPO is optional there" which is accurate but could be slightly confusing. Not a bug.

5. set -euo pipefail interaction with resolve_sha || true (line 102)

The || true guard prevents set -e from killing the script when resolve_sha returns non-zero. This is correct and intentional. Just confirming it's handled properly — it is.

Verdict

Approved with minor suggestions. The fix directly addresses the reported problem (stale failed-run reporting), is safe, well-commented, and doesn't break the existing -q / branch / PR modes. The --arg sha refactor (item 1) is the only change I'd recommend before merge; the others are nice-to-haves.


@Yeraze Yeraze merged commit be6c224 into main Jun 25, 2026
22 of 23 checks passed
@Yeraze Yeraze deleted the fix/watch-ci-filter-by-head-sha branch June 25, 2026 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant