[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6C3DHFVWKAXKXM5RJ1WAHVC":3,"skill-same:01M6C3DHFVWKAXKXM5RJ1WAHVC":116},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":30,"publishedAt":64,"linkOnly":65,"markdownContent":66,"markdownBody":67,"frontMatter":68,"refName":89,"commitSha":90,"skillDirectory":91,"forksCount":92,"repositoryHtmlUrl":93,"repositoryHomepageUrl":6,"ownerHtmlUrl":94,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":95,"install":96,"resources":109},"01M6C3DHFVWKAXKXM5RJ1WAHVC","gh-actions-transient-failure-diagnosis",null,"Diagnose GitHub Actions failures that look like real CI failures but are actually transient infrastructure (HTTP 503 from `actions\u002Fgithub-script`, GitHub API rate-limit surfaced inside workflow steps, cancelled downstream gate propagated as aggregator failure, or webhook-trigger races). Verified 2026-07-20 on PR #8466: Auth Browser Tests job 88252983872 hit 503, and Green Gate aggregator job 88254838835 failed because downstream `smoke_gate_wait` was cancelled -- push empty commit or `gh run rerun`, do NOT chase the named PR or investigate the cancelled job's code.","jleechanorg\u002Fjleechan-skills","jleechanorg","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F221404966?v=4","01M6C1VJ99XG950X1DWPEBCBGX","01M6C1VJDK5YVDNT3362RT5WSB",6,"MIT","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fgh-actions-transient-failure-diagnosis\u002FSKILL.md",[17,22,25],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":26,"categoryName":27,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63],"gh","actions","gh actions transient failure diagnosis","diagnosis","webhook","fetch","smoke","superpowers","aggregator","db","github actions","re","at","api rate","rate","failing","2026","api","workflow","ci","logs","push","pull","code","run","pr","http","diagnose","repos","commit","auth","change","workflows","2026-11-10 10:12:30",false,"---\nname: gh-actions-transient-failure-diagnosis\nversion: 1.2.0\nchangelog:\n  - \"1.2.0 (2026-07-20) CodeRabbit rate-limit chain failure (PR #8462 head 68df3793d8). When Green Gate Precheck fails with GATE-1 CI=failure AND GATE-3 CR=FAIL AND the CodeRabbit status description is exactly `Review rate limited`, the root cause is a single upstream rate-limit — the aggregator's CI=failure is a chained propagation through Gate-3, not a real test failure. Recovery is passive (poll `pulls\u002FN\u002Freviews` while waiting for the rate-limit to clear per env-preferences.mdc 2026-07-16). DO NOT @-mention CodeRabbit or retry the review — that just extends the ETA.\"\ndescription: \"Diagnose GitHub Actions failures that look like real CI failures but are actually transient infrastructure (HTTP 503 from `actions\u002Fgithub-script`, GitHub API rate-limit surfaced inside workflow steps, cancelled downstream gate propagated as aggregator failure, or webhook-trigger races). Verified 2026-07-20 on PR #8466: Auth Browser Tests job 88252983872 hit 503, and Green Gate aggregator job 88254838835 failed because downstream `smoke_gate_wait` was cancelled -- push empty commit or `gh run rerun`, do NOT chase the named PR or investigate the cancelled job's code.\"\ntriggers:\n  - \"actions\u002Fgithub-script 503\"\n  - \"Resolve PR context failure transient\"\n  - \"CI failing but no code change\"\n  - \"same commit two different results\"\n  - \"transient github api failure\"\n  - \"rate limit inside github action\"\n  - \"Green Gate cancelled\"\n  - \"GATE-X FAIL result=cancelled\"\n  - \"aggregator gate downstream cancelled\"\n  - \"smoke_gate_wait cancelled\"\nchangelog:\n  - \"1.1.0 (2026-07-20) Cancelled downstream gate (Green Gate aggregator case). PR #8466 head 7041776da1 aggregator job 88254838835 failed at `GATE-8 FAIL: smoke_gate_wait job did not complete successfully (result=cancelled, smoke_gate=unset)` while Gates 1-6 (`PRECHECK_RESULT: success`) and Gate 4 (`BUGBOT_GATE: PASS`) passed. Recovery: `gh run rerun \u003Crun-db-id> --failed` for workflow_dispatch, empty-commit retrigger for `pull_request`-triggered workflows, wait-and-retry if both re-cancel.\"\n  - \"1.0.0 (2026-07-20) Initial. PR #8466 Auth Browser Tests job 88252983872 hit 503 on `GET \u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466`. Empty commit retrigger cleared the same step. Recipe includes the `actions\u002Fjobs\u002F{id}\u002Flogs` S3-redirect ZIP-fetch path.\"\nrelated_skills:\n  - drive-pr-to-green\n  - gh-actions-slow-runs\n  - gh-actions-stuck-self-hosted-runner-recovery\n  - superpowers-cloud-build\n---\n\n# gh-actions-transient-failure-diagnosis\n\n## Why\n\nGitHub Actions runs occasionally fail for reasons that have nothing to do with the PR's code. The most common shape is **transient GitHub infrastructure** surfacing inside workflow steps: HTTP 503 from `actions\u002Fgithub-script` calls to `api.github.com`, rate-limit hits, or webhook-trigger races. The failure gets attributed to the workflow \u002F PR \u002F commit anyway because that's how `check-runs.conclusion` works — the action framework doesn't distinguish \"the step's API call hit a 503\" from \"the step's code threw.\"\n\n**The trap:** the natural response is to investigate the named PR\u002Fcommit. The user gets a red CI report, the agent looks at the named commit, finds nothing wrong, and the investigation burns budget chasing a phantom. The actual root cause is GitHub's API availability, not the code.\n\n**Verified case (2026-07-20, $GITHUB_REPOSITORY PR #8466):**\n\n- **Symptom:** Auth Browser Tests run #14728 failed at step 2 (`Resolve PR number + head SHA`, job 88252983872). First impression: \"PR #8466 is broken.\"\n- **Actual log:** The step's `actions\u002Fgithub-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea` issue` called `github.rest.pulls.get({owner, repo, pull_number: 8466})`. The server returned `status: 503, response: { url: 'https:\u002F\u002Fapi.github.com\u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466', data: { message: 'No server is currently available to service your request. Sorry about that. Please try resubmitting your request and contact us if the problem persists.' } }`.\n- **Root cause:** GitHub's own API was temporarily unavailable. The PR's code was fine — the failed step never even touched it.\n- **Fix:** Push an empty commit on the PR branch to retrigger the `pull_request` event. The new run started clean and Design Doc Gate passed on the first attempt.\n\n## When to load this skill\n\n- A check-run fails with **only generic** output (\"Process completed with exit code 1\") and no actionable error\n- The only failed step is `actions\u002Fgithub-script` or a step whose primary action is to call `github.rest.*` or `api.github.com`\n- The same commit produces **different results** on consecutive runs (one green, one red, with no code change between)\n- `gh api` or `curl` against `api.github.com` returns 503 \u002F 502 \u002F secondary rate-limit errors within minutes of the CI failure\n- `gh pr view --json statusCheckRollup` shows an inconsistent picture (e.g. `success` from one workflow but `failure` from a different one for the same SHA)\n- User asks \"is this a real CI failure or just a flake?\"\n- **Green Gate Precheck shows `GATE-1 FAIL: CI=failure` AND `GATE-3 FAIL: CR=FAIL(status=failure comment=none)` AND `CodeRabbit` status context has description `Review rate limited`** — see \"CodeRabbit rate-limit chain failure\" below.\n\n## Diagnostic recipe (30-90 seconds)\n\n### Step 1: Identify the failed check-run and its parent workflow run\n\n```bash\nTOKEN=\"$(gh auth token)\"\n# Pick the failing check_run_id from `gh pr view --json statusCheckRollup` or\n# `gh api repos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcommits\u002F\u003CHEAD_SHA>\u002Fcheck-runs?per_page=20`\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcheck-runs\u002F\u003CID>\" | jq .\n```\n\nThe response includes `check_suite.workflow_id`, `check_suite.id` (= the workflow run's databaseId), and `details_url` (the run's HTML page).\n\n### Step 2: Find the failed step inside the workflow run\n\n```bash\n# Get the workflow run's jobs (use the run databaseId from step 1, NOT the run_number)\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Factions\u002Fruns\u002F\u003CRUN_DATABASE_ID>\u002Fjobs\" | jq '.jobs[] | {id, name, conclusion, steps: [.steps[] | select(.conclusion==\"failure\") | {name, number}]}'\n```\n\nNote the `job.id` and the failing step's `number`. **Pitfall:** `gh pr view --json statusCheckRollup` gives `run_number` (human-readable like `#14728`), but `actions\u002Fjobs\u002F\u003Cid>\u002Flogs` requires the **run's databaseId** (long numeric like `29709994853`). Always use databaseId for `actions\u002Fruns\u002F\u003Cid>\u002Fjobs`.\n\n### Step 3: Fetch the failing step's log\n\n```bash\n# actions\u002Fjobs\u002F\u003Cjob_id>\u002Flogs returns a 302 → S3-signed-URL ZIP archive\n# The follow-the-redirect with -L pulls the actual log bytes\ncurl -fsS -L -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Factions\u002Fjobs\u002F\u003CJOB_ID>\u002Flogs\" \\\n  -o \u002Ftmp\u002Fjob-log.bin\n# The response is a ZIP archive\npython3 -c \"\nimport zipfile, io\nwith zipfile.ZipFile('\u002Ftmp\u002Fjob-log.bin') as zf:\n    for name in zf.namelist():\n        print(f'=== {name} ===')\n        print(zf.read(name).decode('utf-8', 'replace'))\n\"\n```\n\n### Step 4: Look for the transient-failure signature\n\nSearch the log for these patterns (any one is a strong \"this is transient\" signal):\n\n| Signature | Meaning |\n|-----------|---------|\n| `status: 503` + `message: 'No server is currently available to service your request'` | GitHub API degraded |\n| `status: 429` + `x-ratelimit-remaining: 0` | Per-token rate limit hit inside the workflow |\n| `status: 502` + `Bad Gateway` | GitHub infrastructure blip |\n| `Failed to start the runner` \u002F `Failed to create worktree` | Self-hosted runner hiccup, not code |\n| `Conflict: Another merge in progress` | Race against a concurrent merge \u002F push |\n| `The operation was canceled.` mid-step | Runner preempted or spot-killed |\n| Empty `output.title` + empty `output.text` + `conclusion: failure` on a check-run | Workflow job died before writing any output — almost always transient infra |\n| Aggregator job (`Green Gate`, `N-green`, etc.) fails with `\u003CGATE-X> FAIL: \u003Cgate_name> job did not complete successfully (result=cancelled, …)` | Downstream gate job was **cancelled** (not failed). Aggregator propagates cancel as fail. See \"Cancelled downstream gate\" recipe below. |\n\nIf you see `status: 503` from an `actions\u002Fgithub-script` step calling `github.rest.pulls.get`, `github.rest.issues.*`, `github.rest.repos.*`, etc. → **it is transient. Push an empty commit to retrigger.**\n\n### Step 4b: Cancelled downstream gate (Green Gate aggregator case)\n\nWhen the failing check is an aggregator like `Green Gate` and its log shows:\n```\nGATE-X FAIL: \u003Cgate_name>_wait job did not complete successfully (result=cancelled, \u003Cgate_name>_gate=unset)\n```\n\n…the aggregator propagated a `cancelled` from a downstream gate. This is **almost always transient** — typically caused by a parallel job's runner pool exhaustion, a self-hosted runner preemption, or an upstream merge conflict that cancelled sibling jobs. The aggregator's `exit 1` is correct behavior (it's fail-closed on missing input), but the underlying gate is innocent.\n\n**Verified case (2026-07-20, $GITHUB_REPOSITORY PR #8466 head `7041776da1`):**\n\n- Aggregator job #88254838835 (`Green Gate`) failed at the `GATE-8 FAIL` step.\n- The aggregator's log line was: `GATE-8 FAIL: smoke_gate_wait job did not complete successfully (result=cancelled, smoke_gate=unset) — see that job's log for the detailed GATE-8 WAIT\u002FFAIL trace`\n- All other aggregator gates passed: `PRECHECK_RESULT: success` (Gates 1-6), `BUGBOT_GATE: PASS` (Gate 4).\n- The cancelled `smoke_gate_wait` (Gate 8) had no log of its own — it was cancelled before producing output.\n\n**Recipe** (in priority order — try the cheapest first):\n\n1. **`gh run rerun \u003Crun-databaseId> --failed` on the aggregator** — re-runs only the failed jobs in the same workflow. CAVEAT: this only works for `workflow_dispatch` runs and same-workflow cancellations; for `pull_request`-triggered runs (Green Gate is one), the rerun reuses the cancelled sibling's state and may cancel again.\n2. **Empty-commit retrigger** (preferred for `pull_request`-triggered workflows) — pushes a new SHA that re-fires the `pull_request` event for all dependent workflows. Use the same recipe from Step 5 below; the cancelled sibling usually re-runs clean because the runner-pool pressure has cleared.\n3. **Re-dispatch the cancelled job directly via `gh workflow run \u003Cworkflow>.yml -f pr_number=\u003CN>`** — but ONLY for the cancelled job's workflow, not Green Gate itself. The dispatcher lands on `head_branch=main` per `drive-pr-to-green` v2.5.6, so verify `headBranch` after dispatch.\n4. **Wait and retry** — if (1)–(3) all re-cancel, the runner pool is genuinely saturated. Wait 15-30 min and re-trigger; do not loop rapidly.\n\n**Diagnostic commands:**\n\n```bash\n# Find the aggregator run + its cancelled downstream\nTOKEN=\"$(gh auth token)\"\ngh pr view \u003CN> --repo \u003COWNER>\u002F\u003CREPO> --json statusCheckRollup\n# → aggregator check_run_id (e.g., Green Gate)\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcheck-runs\u002F\u003Caggregator_id>\" \\\n  | jq '.check_suite'\n# → aggregator run databaseId\n\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Factions\u002Fruns\u002F\u003Caggregator_run_db_id>\u002Fjobs\" \\\n  | jq '.jobs[] | {id, name, conclusion}' | grep -i cancelled\n# → the cancelled downstream's job_id\n\n# Try (1) first\ngh run rerun \u003Caggregator_run_db_id> --failed\n# If the rerun also cancels, drop to (2): empty commit\ngit -c user.name=\"\u003Cu>\" -c user.email=\"\u003Cu>@users.noreply.github.com\" \\\n    commit --allow-empty -m \"ci: retrigger after Gate-8 cancel in aggregator run \u003Cid>\"\ngit push origin HEAD\n```\n\n**Anti-pattern:** investigating the cancelled job's *code* (e.g., reading the smoke test config) before checking whether the cancel was a runner-pool issue. Cancellations with no log output are almost never caused by the workflow's own logic — they're caused by the runner allocation system or by a sibling job's failure cascading. Read the aggregator's gate-trace line first; if it says `result=cancelled`, the gate code is fine.\n\n### Step 4c: CodeRabbit rate-limit chain failure (NEW v1.2.0)\n\nWhen the Green Gate Precheck fails with this exact pattern in its log:\n\n```\nGATE-1 FAIL: CI=failure\nGATE-2 PASS: no conflicts\nGATE-3 FAIL: CR=FAIL(status=failure comment=none)\nGATE-5 PASS: all comments resolved\n```\n\n…and `gh api repos\u002F\u003Co>\u002F\u003Cr>\u002Fcommits\u002F\u003Csha>\u002Fstatus` shows a `CodeRabbit` StatusContext with `state: failure` and `description: \"Review rate limited\"`, the failure class is **CodeRabbit rate-limit chain-failure**, NOT a real CI failure.\n\n**What's happening**: CodeRabbit is throttled by its org\u002Faccount-level \"Fair Usage Limits Policy\" (per env-preferences.mdc 2026-07-16). Its status_context reports `state=failure` because it couldn't even submit a review. Green Gate Precheck's GATE-3 check reads that `state=failure` and reports `GATE-3 FAIL`. Because all gates must pass for `eligible=true` (any FAIL → `ELIGIBLE=false`), and the precheck also rolls up `GATE-1` from any non-success check_run in the head SHA, **GATE-1 FAILS as a downstream propagation of GATE-3**. The actual CI tests are green (`Directory tests core-mvp-1\u002F2\u002F3`, `Directory tests core-tests`, `Coverage Report`, all PASS).\n\n**Recipe** (in priority order):\n\n1. **Verify the chain** by reading the aggregator's gate-trace (NOT the failed check_runs — the chain hides in the rolled-up state). The pattern `GATE-3 FAIL: CR=FAIL(status=failure comment=none)` followed by `GATE-1 FAIL: CI=failure` is the signature; `failure` in the CR tuple is from the StatusContext, not from a missing review.\n\n2. **Check `.coderabbit.yaml` at repo root** to confirm the ceiling:\n   ```bash\n   gh api repos\u002F\u003Co>\u002F\u003Cr>\u002Fcontents\u002F.coderabbit.yaml -H \"Accept: application\u002Fvnd.github.v3.raw\" | grep -A2 \"^reviews:\"\n   # `approve: true` → CodeRabbit CAN post APPROVED when not rate-limited\n   # `approve` absent → CodeRabbit can only post COMMENTED (no merit to waiting longer)\n   ```\n\n3. **Do NOT retry CodeRabbit via PR comment** (`@coderabbitai full review`). Per env-preferences.mdc 2026-07-16: \"once rate-limited, stop retrying that PR; poll `pulls\u002FN\u002Freviews` passively instead. Retrying `@coderabbitai full review` while still rate-limited re-extends the ETA instead of resetting it (observed 18min→57min→58min).\"\n\n4. **Poll `pulls\u002FN\u002Freviews` every 5-10 min** via REST (GraphQL is the same rate-limited bucket as the SDK):\n   ```bash\n   gh api repos\u002F\u003Co>\u002F\u003Cr>\u002Fpulls\u002F\u003CN>\u002Freviews --paginate \\\n     | jq -r --arg head \"\u003CHEAD_SHA>\" \\\n       '.[] | select(.user.login == \"coderabbitai[bot]\" and .commit_id == $head) | \"\\(.state) \\(.submitted_at)\"'\n   ```\n\n5. **When a new review appears with `state: APPROVED`** (or `state: COMMENTED` if `.coderabbit.yaml` lacks `approve: true`), the chain resolves on its own — GATE-3 transitions to PASS, the aggregator re-reads and GATE-1 also PASSes. No further action needed; Green Gate's precheck re-runs on the next `pull_request` event or via `gh workflow run green-gate.yml --ref \u003Cbranch>`.\n\n**Anti-pattern:** the chain-failure pattern looks identical to \"the PR has bad code\". An agent that doesn't read the aggregator's gate-trace will:\n- read the named PR's diff looking for the bug\n- find nothing\n- propose editing bq_logging.py\n- push a fix commit\n- retrigger CI\n- now there's a polluted commit + the rate-limit is still happening + nothing got better\n\nRead the gate log FIRST. The shape `GATE-3 FAIL: CR=FAIL(status=failure comment=none)` + `CodeRabbit description: \"Review rate limited\"` is the entire signal.\n\n**Verified case (2026-07-20, $GITHUB_REPOSITORY PR #8462 head 68df3793d8):**\n\n- Aggregator log showed `GATE-1 FAIL: CI=failure` and `GATE-3 FAIL: CR=FAIL(status=failure comment=none)`.\n- 17 check_runs succeeded (`Directory tests core-mvp-1\u002F2\u002F3`, `Directory tests core-tests`, `Coverage Report`, `Import Validation`, `Schema Coverage Guard`, etc.); 2 failed (`Green Gate`, `Green Gate Precheck (Gates 1-6)`); 6 skipped (`Bugbot Gate Wait (Gate 4)`, `Smoke Gate Wait (Gate 8)`, `Cursor Bugbot`); 1 in-progress at the snapshot (`MCP Smoke Tests [Preview E2E]`).\n- CodeRabbit status (REST `commits\u002F\u003Csha>\u002Fstatus`): `CodeRabbit state=failure desc=Review rate limited`.\n- Resolution: passive wait, no further action from agent. The CR rate-limit clears org-wide on its own schedule; once it does, a new review is posted on the next push\u002Fauto-retrigger.\n\n### Step 5: Verify with local validator (before retriggering)\n\nBefore pushing, double-check the PR body \u002F commit \u002F branch actually pass the gate that failed. For design-doc-gate-style regex checks, run the local validator script from the workflow's source:\n\n```bash\n# Example: design-doc-gate regex from .github\u002Fworkflows\u002Fdesign-doc-gate.yml\nRE='^[[:space:]]*##[[:space:]]+(design[[:space:]]+decision|governing[[:space:]]+design[[:space:]]+doc[[:space:]]*&[[:space:]]+tracking|tenets)([[:space:]]|$)'\ngh pr view \u003CN> --repo \u003COWNER>\u002F\u003CREPO> --json body | jq -r '.body' | grep -iP \"$RE\" || echo \"FAIL\"\n```\n\nIf the local validator PASSES on the same head SHA but CI FAILED with the same content check, the CI failure is transient (a stale run against an older commit, or a 503-induced early-exit). Safe to retrigger.\n\n## The fix — empty-commit retrigger\n\n```bash\n# From inside the PR's worktree (or a clean clone on the PR's branch)\ngit -c user.name=\"\u003Cgithub-username>\" \\\n    -c user.email=\"\u003Cgithub-username>@users.noreply.github.com\" \\\n    commit --allow-empty -m \"ci: retrigger after transient github 503 in \u003Cstep-name> (job \u003Cjob-id>)\"\ngit push origin HEAD\n```\n\nThis creates a new commit SHA on the PR's `headRefName`, which fires the `pull_request` event with the correct SHA. All workflows that watch `pull_request` will re-evaluate. The new run starts fresh, gets a new runner allocation, and the transient API issue usually clears within minutes.\n\n**Risk:** this modifies the PR's branch with an empty commit. For dependabot\u002Fexternal-author PRs where you don't have write access, use `@dependabot rebase` instead, or `gh run rerun` if the original was a `workflow_dispatch` (note: `pull_request`-triggered runs cannot be rerun — they must be re-triggered by a push).\n\n**Note on `gh workflow run`:** `gh workflow run \u003Cworkflow>.yml -f pr_number=N -f head_sha=X` lands on `head_branch=main`, NOT the PR branch. The dispatch evaluates against `origin\u002Fmain`'s HEAD, not the PR's. The resulting run is useless for refreshing PR status. See `drive-pr-to-green` v2.5.6 for the full trap. **Empty commit is the canonical fix.**\n\n## Anti-patterns\n\n- ❌ **Chasing the named PR\u002Fcommit without reading the log.** If the only failed step is `actions\u002Fgithub-script` and the log shows `status: 503`, the named PR has nothing to do with it. Verify the log before opening an investigation.\n- ❌ **`gh workflow run` to \"refresh\" PR status.** It does not — see `drive-pr-to-green` v2.5.6 for why the dispatch evaluates against `origin\u002Fmain`.\n- ❌ **Adding `--retry` flags to the failing step.** `actions\u002Fgithub-script` already retries once. A third retry will not help; the underlying API availability issue is on GitHub's side.\n- ❌ **Reverting a real PR change because of a transient CI failure.** The code is fine; the CI is flaky. Push empty commit, let CI re-run, merge.\n- ❌ **Trusting `gh pr view --json statusCheckRollup` alone for \"is CI green?\"** It only shows the latest, deduplicated view. Always fetch `\u002Fcommits\u002F{sha}\u002Fcheck-runs?per_page=50` for the modern GH Actions API and look at `check_runs[].conclusion` directly. A `statusCheckRollup: success` from a single `CodeRabbit` legacy status_context can coexist with multiple GH Actions failures.\n\n## Verification — proving the empty-commit retrigger worked\n\nAfter pushing the empty commit:\n\n```bash\n# Wait ~30s for CI to queue, then check the new head's check-runs\nNEW_SHA=$(git -C \u003Cworktree> rev-parse HEAD)\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcommits\u002F$NEW_SHA\u002Fcheck-runs?per_page=20\" \\\n  | jq '.check_runs[] | {name, status, conclusion, details_url}'\n```\n\nExpected: `Design Doc Gate` and any previously-failing check should show `status: in_progress` or `conclusion: success` on the NEW SHA, distinct from the prior SHA's red state. If the same check fails again with the same `status: 503` log → escalate to \"GitHub is having a bad day\" and pause the drive until the incident clears.\n\n## Pair with\n\n- `drive-pr-to-green` v2.5.6 — empty-commit retrigger + `gh workflow run` `head_branch` trap (this skill's empty-commit section is the detailed recipe; the drive skill is the lifecycle context)\n- `gh-actions-slow-runs` — when the failure is \"slow\" not \"red\" (runner pool saturation, billing spikes, label-match fallthrough)\n- `gh-actions-stuck-self-hosted-runner-recovery` — when the failure is \"no log retrievable\" (runner died, `BlobNotFound` on the log endpoint)\n- `superpowers-cloud-build` Step 0 hello-world — same \"don't trust the headline, fetch the log\" discipline applied to cloud-build runs (verify `Cloud Build \u003Csupervisor@cloud-build.local>` actually committed before reporting success)\n\n## Reference\n\n- Verified reproduction: PR #8466 \u002F job #88252983872 \u002F run #29709994853, 2026-07-20\n  - Failed step log excerpt: `status: 503, response: { url: 'https:\u002F\u002Fapi.github.com\u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466', data: { message: 'No server is currently available to service your request.' } }, request: { method: 'GET', url: 'https:\u002F\u002Fapi.github.com\u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466' }`\n  - Fix: `git -c user.name=jleechan2015 -c user.email=jleechan2015@users.noreply.github.com commit --allow-empty -m 'ci: retrigger after transient github 503 in Resolve PR context (job 88252983872)' && git push origin HEAD`\n  - Result: `60c050a48b..7041776da1  HEAD -> fix\u002F8353-cloudbuild-json-sanitize`. New run #29710376998 (Design Doc Gate) passed on the new head; subsequent queued runs started against `7041776da1`.\n\n- **Cancelled downstream gate (Green Gate aggregator)**: PR #8466 \u002F aggregator job #88254838835 \u002F run #29710376992 on head `7041776da1`, 2026-07-20\n  - Aggregator log: `GATE-8 FAIL: smoke_gate_wait job did not complete successfully (result=cancelled, smoke_gate=unset) — see that job's log for the detailed GATE-8 WAIT\u002FFAIL trace` at 02:15:01.9548046Z\n  - Aggregator passed Gates 1-6 (`PRECHECK_RESULT: success`) and Gate 4 (`BUGBOT_GATE: PASS`). Only Gate 8 was cancelled.\n  - Recovery: `gh run rerun \u003Caggregator_run_db_id> --failed` or empty-commit retrigger per Step 4b above.\n  - Full recipe: `references\u002Fcancelled-downstream-gate-2026-07-20.md`.\n\n- **Sibling transient case (different root cause, same PR)**: PR #8466 \u002F Auth Browser Tests job #88252983872 \u002F run #29709994853 hit the v1.0.0 transient-503 trap on `actions\u002Fgithub-script` step. Empty commit to `7041776da1` cleared it. Both transient-failure shapes occurred on the same PR within minutes — verify the aggregator's gate-trace line AND the failed step's log before deciding the root cause class.","# gh-actions-transient-failure-diagnosis\n\n## Why\n\nGitHub Actions runs occasionally fail for reasons that have nothing to do with the PR's code. The most common shape is **transient GitHub infrastructure** surfacing inside workflow steps: HTTP 503 from `actions\u002Fgithub-script` calls to `api.github.com`, rate-limit hits, or webhook-trigger races. The failure gets attributed to the workflow \u002F PR \u002F commit anyway because that's how `check-runs.conclusion` works — the action framework doesn't distinguish \"the step's API call hit a 503\" from \"the step's code threw.\"\n\n**The trap:** the natural response is to investigate the named PR\u002Fcommit. The user gets a red CI report, the agent looks at the named commit, finds nothing wrong, and the investigation burns budget chasing a phantom. The actual root cause is GitHub's API availability, not the code.\n\n**Verified case (2026-07-20, $GITHUB_REPOSITORY PR #8466):**\n\n- **Symptom:** Auth Browser Tests run #14728 failed at step 2 (`Resolve PR number + head SHA`, job 88252983872). First impression: \"PR #8466 is broken.\"\n- **Actual log:** The step's `actions\u002Fgithub-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea` issue` called `github.rest.pulls.get({owner, repo, pull_number: 8466})`. The server returned `status: 503, response: { url: 'https:\u002F\u002Fapi.github.com\u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466', data: { message: 'No server is currently available to service your request. Sorry about that. Please try resubmitting your request and contact us if the problem persists.' } }`.\n- **Root cause:** GitHub's own API was temporarily unavailable. The PR's code was fine — the failed step never even touched it.\n- **Fix:** Push an empty commit on the PR branch to retrigger the `pull_request` event. The new run started clean and Design Doc Gate passed on the first attempt.\n\n## When to load this skill\n\n- A check-run fails with **only generic** output (\"Process completed with exit code 1\") and no actionable error\n- The only failed step is `actions\u002Fgithub-script` or a step whose primary action is to call `github.rest.*` or `api.github.com`\n- The same commit produces **different results** on consecutive runs (one green, one red, with no code change between)\n- `gh api` or `curl` against `api.github.com` returns 503 \u002F 502 \u002F secondary rate-limit errors within minutes of the CI failure\n- `gh pr view --json statusCheckRollup` shows an inconsistent picture (e.g. `success` from one workflow but `failure` from a different one for the same SHA)\n- User asks \"is this a real CI failure or just a flake?\"\n- **Green Gate Precheck shows `GATE-1 FAIL: CI=failure` AND `GATE-3 FAIL: CR=FAIL(status=failure comment=none)` AND `CodeRabbit` status context has description `Review rate limited`** — see \"CodeRabbit rate-limit chain failure\" below.\n\n## Diagnostic recipe (30-90 seconds)\n\n### Step 1: Identify the failed check-run and its parent workflow run\n\n```bash\nTOKEN=\"$(gh auth token)\"\n# Pick the failing check_run_id from `gh pr view --json statusCheckRollup` or\n# `gh api repos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcommits\u002F\u003CHEAD_SHA>\u002Fcheck-runs?per_page=20`\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcheck-runs\u002F\u003CID>\" | jq .\n```\n\nThe response includes `check_suite.workflow_id`, `check_suite.id` (= the workflow run's databaseId), and `details_url` (the run's HTML page).\n\n### Step 2: Find the failed step inside the workflow run\n\n```bash\n# Get the workflow run's jobs (use the run databaseId from step 1, NOT the run_number)\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Factions\u002Fruns\u002F\u003CRUN_DATABASE_ID>\u002Fjobs\" | jq '.jobs[] | {id, name, conclusion, steps: [.steps[] | select(.conclusion==\"failure\") | {name, number}]}'\n```\n\nNote the `job.id` and the failing step's `number`. **Pitfall:** `gh pr view --json statusCheckRollup` gives `run_number` (human-readable like `#14728`), but `actions\u002Fjobs\u002F\u003Cid>\u002Flogs` requires the **run's databaseId** (long numeric like `29709994853`). Always use databaseId for `actions\u002Fruns\u002F\u003Cid>\u002Fjobs`.\n\n### Step 3: Fetch the failing step's log\n\n```bash\n# actions\u002Fjobs\u002F\u003Cjob_id>\u002Flogs returns a 302 → S3-signed-URL ZIP archive\n# The follow-the-redirect with -L pulls the actual log bytes\ncurl -fsS -L -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Factions\u002Fjobs\u002F\u003CJOB_ID>\u002Flogs\" \\\n  -o \u002Ftmp\u002Fjob-log.bin\n# The response is a ZIP archive\npython3 -c \"\nimport zipfile, io\nwith zipfile.ZipFile('\u002Ftmp\u002Fjob-log.bin') as zf:\n    for name in zf.namelist():\n        print(f'=== {name} ===')\n        print(zf.read(name).decode('utf-8', 'replace'))\n\"\n```\n\n### Step 4: Look for the transient-failure signature\n\nSearch the log for these patterns (any one is a strong \"this is transient\" signal):\n\n| Signature | Meaning |\n|-----------|---------|\n| `status: 503` + `message: 'No server is currently available to service your request'` | GitHub API degraded |\n| `status: 429` + `x-ratelimit-remaining: 0` | Per-token rate limit hit inside the workflow |\n| `status: 502` + `Bad Gateway` | GitHub infrastructure blip |\n| `Failed to start the runner` \u002F `Failed to create worktree` | Self-hosted runner hiccup, not code |\n| `Conflict: Another merge in progress` | Race against a concurrent merge \u002F push |\n| `The operation was canceled.` mid-step | Runner preempted or spot-killed |\n| Empty `output.title` + empty `output.text` + `conclusion: failure` on a check-run | Workflow job died before writing any output — almost always transient infra |\n| Aggregator job (`Green Gate`, `N-green`, etc.) fails with `\u003CGATE-X> FAIL: \u003Cgate_name> job did not complete successfully (result=cancelled, …)` | Downstream gate job was **cancelled** (not failed). Aggregator propagates cancel as fail. See \"Cancelled downstream gate\" recipe below. |\n\nIf you see `status: 503` from an `actions\u002Fgithub-script` step calling `github.rest.pulls.get`, `github.rest.issues.*`, `github.rest.repos.*`, etc. → **it is transient. Push an empty commit to retrigger.**\n\n### Step 4b: Cancelled downstream gate (Green Gate aggregator case)\n\nWhen the failing check is an aggregator like `Green Gate` and its log shows:\n```\nGATE-X FAIL: \u003Cgate_name>_wait job did not complete successfully (result=cancelled, \u003Cgate_name>_gate=unset)\n```\n\n…the aggregator propagated a `cancelled` from a downstream gate. This is **almost always transient** — typically caused by a parallel job's runner pool exhaustion, a self-hosted runner preemption, or an upstream merge conflict that cancelled sibling jobs. The aggregator's `exit 1` is correct behavior (it's fail-closed on missing input), but the underlying gate is innocent.\n\n**Verified case (2026-07-20, $GITHUB_REPOSITORY PR #8466 head `7041776da1`):**\n\n- Aggregator job #88254838835 (`Green Gate`) failed at the `GATE-8 FAIL` step.\n- The aggregator's log line was: `GATE-8 FAIL: smoke_gate_wait job did not complete successfully (result=cancelled, smoke_gate=unset) — see that job's log for the detailed GATE-8 WAIT\u002FFAIL trace`\n- All other aggregator gates passed: `PRECHECK_RESULT: success` (Gates 1-6), `BUGBOT_GATE: PASS` (Gate 4).\n- The cancelled `smoke_gate_wait` (Gate 8) had no log of its own — it was cancelled before producing output.\n\n**Recipe** (in priority order — try the cheapest first):\n\n1. **`gh run rerun \u003Crun-databaseId> --failed` on the aggregator** — re-runs only the failed jobs in the same workflow. CAVEAT: this only works for `workflow_dispatch` runs and same-workflow cancellations; for `pull_request`-triggered runs (Green Gate is one), the rerun reuses the cancelled sibling's state and may cancel again.\n2. **Empty-commit retrigger** (preferred for `pull_request`-triggered workflows) — pushes a new SHA that re-fires the `pull_request` event for all dependent workflows. Use the same recipe from Step 5 below; the cancelled sibling usually re-runs clean because the runner-pool pressure has cleared.\n3. **Re-dispatch the cancelled job directly via `gh workflow run \u003Cworkflow>.yml -f pr_number=\u003CN>`** — but ONLY for the cancelled job's workflow, not Green Gate itself. The dispatcher lands on `head_branch=main` per `drive-pr-to-green` v2.5.6, so verify `headBranch` after dispatch.\n4. **Wait and retry** — if (1)–(3) all re-cancel, the runner pool is genuinely saturated. Wait 15-30 min and re-trigger; do not loop rapidly.\n\n**Diagnostic commands:**\n\n```bash\n# Find the aggregator run + its cancelled downstream\nTOKEN=\"$(gh auth token)\"\ngh pr view \u003CN> --repo \u003COWNER>\u002F\u003CREPO> --json statusCheckRollup\n# → aggregator check_run_id (e.g., Green Gate)\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcheck-runs\u002F\u003Caggregator_id>\" \\\n  | jq '.check_suite'\n# → aggregator run databaseId\n\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Factions\u002Fruns\u002F\u003Caggregator_run_db_id>\u002Fjobs\" \\\n  | jq '.jobs[] | {id, name, conclusion}' | grep -i cancelled\n# → the cancelled downstream's job_id\n\n# Try (1) first\ngh run rerun \u003Caggregator_run_db_id> --failed\n# If the rerun also cancels, drop to (2): empty commit\ngit -c user.name=\"\u003Cu>\" -c user.email=\"\u003Cu>@users.noreply.github.com\" \\\n    commit --allow-empty -m \"ci: retrigger after Gate-8 cancel in aggregator run \u003Cid>\"\ngit push origin HEAD\n```\n\n**Anti-pattern:** investigating the cancelled job's *code* (e.g., reading the smoke test config) before checking whether the cancel was a runner-pool issue. Cancellations with no log output are almost never caused by the workflow's own logic — they're caused by the runner allocation system or by a sibling job's failure cascading. Read the aggregator's gate-trace line first; if it says `result=cancelled`, the gate code is fine.\n\n### Step 4c: CodeRabbit rate-limit chain failure (NEW v1.2.0)\n\nWhen the Green Gate Precheck fails with this exact pattern in its log:\n\n```\nGATE-1 FAIL: CI=failure\nGATE-2 PASS: no conflicts\nGATE-3 FAIL: CR=FAIL(status=failure comment=none)\nGATE-5 PASS: all comments resolved\n```\n\n…and `gh api repos\u002F\u003Co>\u002F\u003Cr>\u002Fcommits\u002F\u003Csha>\u002Fstatus` shows a `CodeRabbit` StatusContext with `state: failure` and `description: \"Review rate limited\"`, the failure class is **CodeRabbit rate-limit chain-failure**, NOT a real CI failure.\n\n**What's happening**: CodeRabbit is throttled by its org\u002Faccount-level \"Fair Usage Limits Policy\" (per env-preferences.mdc 2026-07-16). Its status_context reports `state=failure` because it couldn't even submit a review. Green Gate Precheck's GATE-3 check reads that `state=failure` and reports `GATE-3 FAIL`. Because all gates must pass for `eligible=true` (any FAIL → `ELIGIBLE=false`), and the precheck also rolls up `GATE-1` from any non-success check_run in the head SHA, **GATE-1 FAILS as a downstream propagation of GATE-3**. The actual CI tests are green (`Directory tests core-mvp-1\u002F2\u002F3`, `Directory tests core-tests`, `Coverage Report`, all PASS).\n\n**Recipe** (in priority order):\n\n1. **Verify the chain** by reading the aggregator's gate-trace (NOT the failed check_runs — the chain hides in the rolled-up state). The pattern `GATE-3 FAIL: CR=FAIL(status=failure comment=none)` followed by `GATE-1 FAIL: CI=failure` is the signature; `failure` in the CR tuple is from the StatusContext, not from a missing review.\n\n2. **Check `.coderabbit.yaml` at repo root** to confirm the ceiling:\n   ```bash\n   gh api repos\u002F\u003Co>\u002F\u003Cr>\u002Fcontents\u002F.coderabbit.yaml -H \"Accept: application\u002Fvnd.github.v3.raw\" | grep -A2 \"^reviews:\"\n   # `approve: true` → CodeRabbit CAN post APPROVED when not rate-limited\n   # `approve` absent → CodeRabbit can only post COMMENTED (no merit to waiting longer)\n   ```\n\n3. **Do NOT retry CodeRabbit via PR comment** (`@coderabbitai full review`). Per env-preferences.mdc 2026-07-16: \"once rate-limited, stop retrying that PR; poll `pulls\u002FN\u002Freviews` passively instead. Retrying `@coderabbitai full review` while still rate-limited re-extends the ETA instead of resetting it (observed 18min→57min→58min).\"\n\n4. **Poll `pulls\u002FN\u002Freviews` every 5-10 min** via REST (GraphQL is the same rate-limited bucket as the SDK):\n   ```bash\n   gh api repos\u002F\u003Co>\u002F\u003Cr>\u002Fpulls\u002F\u003CN>\u002Freviews --paginate \\\n     | jq -r --arg head \"\u003CHEAD_SHA>\" \\\n       '.[] | select(.user.login == \"coderabbitai[bot]\" and .commit_id == $head) | \"\\(.state) \\(.submitted_at)\"'\n   ```\n\n5. **When a new review appears with `state: APPROVED`** (or `state: COMMENTED` if `.coderabbit.yaml` lacks `approve: true`), the chain resolves on its own — GATE-3 transitions to PASS, the aggregator re-reads and GATE-1 also PASSes. No further action needed; Green Gate's precheck re-runs on the next `pull_request` event or via `gh workflow run green-gate.yml --ref \u003Cbranch>`.\n\n**Anti-pattern:** the chain-failure pattern looks identical to \"the PR has bad code\". An agent that doesn't read the aggregator's gate-trace will:\n- read the named PR's diff looking for the bug\n- find nothing\n- propose editing bq_logging.py\n- push a fix commit\n- retrigger CI\n- now there's a polluted commit + the rate-limit is still happening + nothing got better\n\nRead the gate log FIRST. The shape `GATE-3 FAIL: CR=FAIL(status=failure comment=none)` + `CodeRabbit description: \"Review rate limited\"` is the entire signal.\n\n**Verified case (2026-07-20, $GITHUB_REPOSITORY PR #8462 head 68df3793d8):**\n\n- Aggregator log showed `GATE-1 FAIL: CI=failure` and `GATE-3 FAIL: CR=FAIL(status=failure comment=none)`.\n- 17 check_runs succeeded (`Directory tests core-mvp-1\u002F2\u002F3`, `Directory tests core-tests`, `Coverage Report`, `Import Validation`, `Schema Coverage Guard`, etc.); 2 failed (`Green Gate`, `Green Gate Precheck (Gates 1-6)`); 6 skipped (`Bugbot Gate Wait (Gate 4)`, `Smoke Gate Wait (Gate 8)`, `Cursor Bugbot`); 1 in-progress at the snapshot (`MCP Smoke Tests [Preview E2E]`).\n- CodeRabbit status (REST `commits\u002F\u003Csha>\u002Fstatus`): `CodeRabbit state=failure desc=Review rate limited`.\n- Resolution: passive wait, no further action from agent. The CR rate-limit clears org-wide on its own schedule; once it does, a new review is posted on the next push\u002Fauto-retrigger.\n\n### Step 5: Verify with local validator (before retriggering)\n\nBefore pushing, double-check the PR body \u002F commit \u002F branch actually pass the gate that failed. For design-doc-gate-style regex checks, run the local validator script from the workflow's source:\n\n```bash\n# Example: design-doc-gate regex from .github\u002Fworkflows\u002Fdesign-doc-gate.yml\nRE='^[[:space:]]*##[[:space:]]+(design[[:space:]]+decision|governing[[:space:]]+design[[:space:]]+doc[[:space:]]*&[[:space:]]+tracking|tenets)([[:space:]]|$)'\ngh pr view \u003CN> --repo \u003COWNER>\u002F\u003CREPO> --json body | jq -r '.body' | grep -iP \"$RE\" || echo \"FAIL\"\n```\n\nIf the local validator PASSES on the same head SHA but CI FAILED with the same content check, the CI failure is transient (a stale run against an older commit, or a 503-induced early-exit). Safe to retrigger.\n\n## The fix — empty-commit retrigger\n\n```bash\n# From inside the PR's worktree (or a clean clone on the PR's branch)\ngit -c user.name=\"\u003Cgithub-username>\" \\\n    -c user.email=\"\u003Cgithub-username>@users.noreply.github.com\" \\\n    commit --allow-empty -m \"ci: retrigger after transient github 503 in \u003Cstep-name> (job \u003Cjob-id>)\"\ngit push origin HEAD\n```\n\nThis creates a new commit SHA on the PR's `headRefName`, which fires the `pull_request` event with the correct SHA. All workflows that watch `pull_request` will re-evaluate. The new run starts fresh, gets a new runner allocation, and the transient API issue usually clears within minutes.\n\n**Risk:** this modifies the PR's branch with an empty commit. For dependabot\u002Fexternal-author PRs where you don't have write access, use `@dependabot rebase` instead, or `gh run rerun` if the original was a `workflow_dispatch` (note: `pull_request`-triggered runs cannot be rerun — they must be re-triggered by a push).\n\n**Note on `gh workflow run`:** `gh workflow run \u003Cworkflow>.yml -f pr_number=N -f head_sha=X` lands on `head_branch=main`, NOT the PR branch. The dispatch evaluates against `origin\u002Fmain`'s HEAD, not the PR's. The resulting run is useless for refreshing PR status. See `drive-pr-to-green` v2.5.6 for the full trap. **Empty commit is the canonical fix.**\n\n## Anti-patterns\n\n- ❌ **Chasing the named PR\u002Fcommit without reading the log.** If the only failed step is `actions\u002Fgithub-script` and the log shows `status: 503`, the named PR has nothing to do with it. Verify the log before opening an investigation.\n- ❌ **`gh workflow run` to \"refresh\" PR status.** It does not — see `drive-pr-to-green` v2.5.6 for why the dispatch evaluates against `origin\u002Fmain`.\n- ❌ **Adding `--retry` flags to the failing step.** `actions\u002Fgithub-script` already retries once. A third retry will not help; the underlying API availability issue is on GitHub's side.\n- ❌ **Reverting a real PR change because of a transient CI failure.** The code is fine; the CI is flaky. Push empty commit, let CI re-run, merge.\n- ❌ **Trusting `gh pr view --json statusCheckRollup` alone for \"is CI green?\"** It only shows the latest, deduplicated view. Always fetch `\u002Fcommits\u002F{sha}\u002Fcheck-runs?per_page=50` for the modern GH Actions API and look at `check_runs[].conclusion` directly. A `statusCheckRollup: success` from a single `CodeRabbit` legacy status_context can coexist with multiple GH Actions failures.\n\n## Verification — proving the empty-commit retrigger worked\n\nAfter pushing the empty commit:\n\n```bash\n# Wait ~30s for CI to queue, then check the new head's check-runs\nNEW_SHA=$(git -C \u003Cworktree> rev-parse HEAD)\ncurl -fsS -H \"Authorization: Bearer $TOKEN\" \\\n  \"https:\u002F\u002Fapi.github.com\u002Frepos\u002F\u003COWNER>\u002F\u003CREPO>\u002Fcommits\u002F$NEW_SHA\u002Fcheck-runs?per_page=20\" \\\n  | jq '.check_runs[] | {name, status, conclusion, details_url}'\n```\n\nExpected: `Design Doc Gate` and any previously-failing check should show `status: in_progress` or `conclusion: success` on the NEW SHA, distinct from the prior SHA's red state. If the same check fails again with the same `status: 503` log → escalate to \"GitHub is having a bad day\" and pause the drive until the incident clears.\n\n## Pair with\n\n- `drive-pr-to-green` v2.5.6 — empty-commit retrigger + `gh workflow run` `head_branch` trap (this skill's empty-commit section is the detailed recipe; the drive skill is the lifecycle context)\n- `gh-actions-slow-runs` — when the failure is \"slow\" not \"red\" (runner pool saturation, billing spikes, label-match fallthrough)\n- `gh-actions-stuck-self-hosted-runner-recovery` — when the failure is \"no log retrievable\" (runner died, `BlobNotFound` on the log endpoint)\n- `superpowers-cloud-build` Step 0 hello-world — same \"don't trust the headline, fetch the log\" discipline applied to cloud-build runs (verify `Cloud Build \u003Csupervisor@cloud-build.local>` actually committed before reporting success)\n\n## Reference\n\n- Verified reproduction: PR #8466 \u002F job #88252983872 \u002F run #29709994853, 2026-07-20\n  - Failed step log excerpt: `status: 503, response: { url: 'https:\u002F\u002Fapi.github.com\u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466', data: { message: 'No server is currently available to service your request.' } }, request: { method: 'GET', url: 'https:\u002F\u002Fapi.github.com\u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466' }`\n  - Fix: `git -c user.name=jleechan2015 -c user.email=jleechan2015@users.noreply.github.com commit --allow-empty -m 'ci: retrigger after transient github 503 in Resolve PR context (job 88252983872)' && git push origin HEAD`\n  - Result: `60c050a48b..7041776da1  HEAD -> fix\u002F8353-cloudbuild-json-sanitize`. New run #29710376998 (Design Doc Gate) passed on the new head; subsequent queued runs started against `7041776da1`.\n\n- **Cancelled downstream gate (Green Gate aggregator)**: PR #8466 \u002F aggregator job #88254838835 \u002F run #29710376992 on head `7041776da1`, 2026-07-20\n  - Aggregator log: `GATE-8 FAIL: smoke_gate_wait job did not complete successfully (result=cancelled, smoke_gate=unset) — see that job's log for the detailed GATE-8 WAIT\u002FFAIL trace` at 02:15:01.9548046Z\n  - Aggregator passed Gates 1-6 (`PRECHECK_RESULT: success`) and Gate 4 (`BUGBOT_GATE: PASS`). Only Gate 8 was cancelled.\n  - Recovery: `gh run rerun \u003Caggregator_run_db_id> --failed` or empty-commit retrigger per Step 4b above.\n  - Full recipe: `references\u002Fcancelled-downstream-gate-2026-07-20.md`.\n\n- **Sibling transient case (different root cause, same PR)**: PR #8466 \u002F Auth Browser Tests job #88252983872 \u002F run #29709994853 hit the v1.0.0 transient-503 trap on `actions\u002Fgithub-script` step. Empty commit to `7041776da1` cleared it. Both transient-failure shapes occurred on the same PR within minutes — verify the aggregator's gate-trace line AND the failed step's log before deciding the root cause class.",{"name":5,"version":69,"triggers":70,"changelog":81,"description":7,"related_skills":84},"1.2.0",[71,72,73,74,75,76,77,78,79,80],"actions\u002Fgithub-script 503","Resolve PR context failure transient","CI failing but no code change","same commit two different results","transient github api failure","rate limit inside github action","Green Gate cancelled","GATE-X FAIL result=cancelled","aggregator gate downstream cancelled","smoke_gate_wait cancelled",[82,83],"1.1.0 (2026-07-20) Cancelled downstream gate (Green Gate aggregator case). PR #8466 head 7041776da1 aggregator job 88254838835 failed at `GATE-8 FAIL: smoke_gate_wait job did not complete successfully (result=cancelled, smoke_gate=unset)` while Gates 1-6 (`PRECHECK_RESULT: success`) and Gate 4 (`BUGBOT_GATE: PASS`) passed. Recovery: `gh run rerun \u003Crun-db-id> --failed` for workflow_dispatch, empty-commit retrigger for `pull_request`-triggered workflows, wait-and-retry if both re-cancel.","1.0.0 (2026-07-20) Initial. PR #8466 Auth Browser Tests job 88252983872 hit 503 on `GET \u002Frepos\u002F$GITHUB_REPOSITORY\u002Fpulls\u002F8466`. Empty commit retrigger cleared the same step. Recipe includes the `actions\u002Fjobs\u002F{id}\u002Flogs` S3-redirect ZIP-fetch path.",[85,86,87,88],"drive-pr-to-green","gh-actions-slow-runs","gh-actions-stuck-self-hosted-runner-recovery","superpowers-cloud-build","main","42ccd02cbf5e8e47839dc3eab6fc754dc6b339cf","hermes\u002Fskills\u002Fgh-actions-transient-failure-diagnosis",0,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills","https:\u002F\u002Fgithub.com\u002Fjleechanorg","2026-11-12 14:45:07",{"source":8,"skillName":5,"commands":97,"prompt":107,"zipAvailable":108},[98,101,104],{"tool":99,"command":100},"npx","npx skills add jleechanorg\u002Fjleechan-skills --skill gh-actions-transient-failure-diagnosis",{"tool":102,"command":103},"bunx","bunx skills add jleechanorg\u002Fjleechan-skills --skill gh-actions-transient-failure-diagnosis",{"tool":105,"command":106},"pnpm","pnpm dlx skills add jleechanorg\u002Fjleechan-skills --skill gh-actions-transient-failure-diagnosis","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fgh-actions-transient-failure-diagnosis\u002FSKILL.md",true,[110],{"path":111,"resourceType":112,"resourceTypeDesc":113,"sizeBytes":114,"sourceHtmlUrl":115},"references\u002Fcancelled-downstream-gate-2026-07-20.md","REFERENCE","参考文档",5784,"https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002Fhermes\u002Fskills\u002Fgh-actions-transient-failure-diagnosis\u002Freferences\u002Fcancelled-downstream-gate-2026-07-20.md",[117,135,155,169,182,198],{"skillNo":118,"name":119,"nameZh":6,"description":120,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":121,"categories":122,"keywords":123,"publishedAt":64},"01M716Y5MTJ6GYJZ2ATXCJK8G4","instinct","Use when the user invokes \u002Finstinct or \u002Fin, or asks to \"message Instinct\", \"ask Instinct\", \"tell Instinct\", \"check Instinct\", or read replies from the configured peer assistant. iMessage first, WhatsApp Web as fallback.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Finstinct\u002FSKILL.md",[],[119,124,125,126,127,128,129,130,131,132,133,134],"user","when","replies","check","use","identity","whatsapp","gmail","read","message","ask",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":144,"publishedAt":64},"01M716Y4HF7FVVMJSA2CCVN5VF","hr","Compatibility alias for history-resume; produce the same fresh-state, provenance-aware resume briefing.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhr\u002FSKILL.md",[141],{"categoryNo":142,"categoryName":143,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000038","人力资源",[137,145,146,147,148,149,150,151,152,153,154],"resume","compatibility","provenance","for","history","produce","alias","briefing","state","fresh",{"skillNo":156,"name":157,"nameZh":6,"description":158,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":159,"categories":160,"keywords":162,"publishedAt":64},"01M716Y1FFNFKFJYYKF8HQA6T8","history-resume","Resume briefing for the current repository branch or worktree using fresh state and admitted history sources.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fhistory-resume\u002FSKILL.md",[161],{"categoryNo":142,"categoryName":143,"taxonomyType":28,"taxonomyTypeDesc":29},[149,145,163,164,124,165,152,166,148,167,168,153,154],"history resume","branch","analysis","repository","worktree","sources",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":173,"categories":174,"keywords":175,"publishedAt":64},"01M716XWXJ8EPHK4RH9T14TR85","grok","Alias for the canonical grokbot browser conversation workflow; \u002Fgrok talks to the Grok bot the same way \u002Fdot talks to Dot.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fgrok\u002FSKILL.md",[],[171,176,177,49,148,178,151,179,180,181],"dot","bot","canonical","conversation","browser","grokbot",{"skillNo":183,"name":184,"nameZh":6,"description":185,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":186,"categories":187,"keywords":188,"publishedAt":64},"01M70N9Q07DTE0EW5KFXMMMSJ0","codexa-bench","Compare `codexa` and normal `codex` on a fair split of comparable work by default; use `--strict` for duplicate runs of the exact same task.","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcodexa-bench\u002FSKILL.md",[],[189,190,191,128,192,148,193,194,195,196,197],"codexa bench","work","split","default","duplicate","task","compare","normal","codex",{"skillNo":199,"name":200,"nameZh":6,"description":201,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":202,"licenseFileUrl":203,"sourceHtmlUrl":204,"categories":205,"keywords":215,"publishedAt":64},"01M6C24S2X60SP8BBFGAEKPEGW","frontend-design","Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML\u002FCSS layouts, or when styling\u002Fbeautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ffrontend-design\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fjleechanorg\u002Fjleechan-skills\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ffrontend-design\u002FSKILL.md",[206,209,212],{"categoryNo":207,"categoryName":208,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":210,"categoryName":211,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":213,"categoryName":214,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000032","设计师",[216,217,218,128,219,220,221,124,222,223,54,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,125,239],"frontend","design","frontend design","generic","ui design","react","html","components","artifacts","ui","aesthetics","Production","web components","landing","applications","quality","web","css","create","examples","with","react components","web ui","pages"]