devops-why-my-pr-not

SkillCloud & infra

Diagnose why CI checks are not running on a PR and/or why a PR cannot be merged by comparing live PR state with current labels, CODEOWNERS, fork trust, and approval configuration. Read-only by default. Use when a developer asks why checks are missing, why a PR cannot merge, or invokes /qv-devops-why-my-pr-not with a PR URL.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the devops-why-my-pr-not skill

What this skill tells your AI

The instructions your AI receives, as published by tetherto/qvac in .agents/skills/qv-devops-why-my-pr-not/SKILL.md and read by ahel’s review.

Self-service triage for the two most common DevOps support questions:

  1. "Why aren't my CI checks running?" — use the canonical CI label and gate docs below; they define which labels, trust rules, and fork restrictions control job execution.
  2. "Why can't I merge?" — use the current approval workflow, CODEOWNERS, and branch-protection state; they define required approvals and merge conditions.

The skill cross-references the live PR state (via gh) against the canonical repo docs that describe the rules:

The docs are the source of truth. The skill quotes them; it does not re-derive their rules.

When to use this skill

Use when:

  • A developer asks "why aren't my checks running on PR #N?"
  • A developer asks "why can't I merge PR #N?" / "what's blocking my PR?"
  • A reviewer asks "what does this PR still need before I can merge it?"
  • User invokes /qv-devops-why-my-pr-not <PR URL>

Do NOT use when:

Inputs

  • Required: PR URL or <owner>/<repo>#<num> shorthand (defaults owner/repo to tetherto/qvac if only #<num> is given).
  • Optional: focus hint — --ci, --merge, or --both (default --both).

If the PR identifier is missing, ask once. Nothing else to ask up-front.

Prerequisites

  • gh CLI installed and authenticated (gh auth status). The token needs repo scope to read PR metadata, checks, and reviews on tetherto/qvac.
  • read:org is not required by the skill itself. If privileged fork jobs fail while recording qvac/fork-verified, check fork-approval logs — the gate job uses github.token with statuses: write (not PAT_TOKEN).

The skill does not require a checked-out worktree. All inspection is via gh.

Safety rules

This skill follows the repository working agreement: read-only by default, with each remote mutation requiring explicit user authorization.

  • Read-only with respect to the user's local working tree. No git switch, git checkout, git reset, git restore, git stash, git pull, git merge, git rebase, git cherry-pick, git clean, gh pr checkout, or any write inside the user's working tree.
  • Read-only with respect to the PR's GitHub state by default. No gh pr edit, no gh api ... -X POST/PATCH/PUT/DELETE, no gh pr comment, no gh pr review without explicit user confirmation per call.
  • Mutations are plan-then-apply. When the diagnosis suggests a fix that the user could perform (apply a label, post /review, request re-review from a teammate, rebase to drop a merge conflict), print the exact gh command, wait for the user to type "yes" / "go" / "apply", then execute. A blanket "do everything" is not accepted — confirm per command.
  • Never approve the fork-ci environment on the user's behalf. That gate is intentionally not self-service. The skill may suggest who to ask (merge/release team), never click approve for them.

Efficiency rules

Cap at 6 shell calls for a normal diagnosis. Cache fetched data once per invocation under /tmp/why-pr-<num>-<short-sha>.json so repeat queries within the same session do not re-hit GitHub.

CallPurpose
1gh pr view <num> --json number,title,state,isDraft,labels,author,baseRefName,headRefName,headRefOid,headRepositoryOwner,headRepository,mergeable,mergeStateStatus,reviewDecision,reviews,statusCheckRollup,latestReviews
2gh pr checks <num> --json name,state,conclusion,workflow,link (or gh api .../check-runs?head_sha=<sha> if gh pr checks is unavailable)
3gh api repos/<owner>/<repo>/commits/<sha>/status when commit statuses are not present in the PR check rollup
4gh api repos/<owner>/<repo>/branches/<base>/protection (only if the user explicitly opts into branch-protection inspection — needs admin/maintain)
5(reserved for plan-then-apply mutation, e.g. gh pr edit --add-label)
6(verification re-read of gh pr view after a mutation)

If a single call covers multiple needs (e.g. gh pr view --json already lists labels and reviews), do not re-fetch.

Workflow

1. Parse and validate the PR identifier

Accept any of:

  • https://github.com/tetherto/qvac/pull/12345
  • tetherto/qvac#12345
  • #12345 → resolves to tetherto/qvac#12345
  • bare 12345 → resolves to tetherto/qvac#12345

Extract <owner>, <repo>, <num>. Reject if any are missing. Print the resolved canonical URL.

2. Read the canonical docs (once per session, cached)

Before fetching the PR, ensure you've read these in this turn (or already have them in context):

These are short. Read them in full. Quote them in findings, do not paraphrase from memory — the rules drift over time and the doc is authoritative.

Read check-approvals.yml when an approval finding is triggered rather than relying on remembered thresholds.

For pod ownership, read .github/teams/devops.json / .github/teams/sdk.json on demand when computing "who can approve fork-ci" or "who is in your CODEOWNERS path."

3. Fetch live PR state

Single call:

gh pr view <num> -R <owner>/<repo> --json number,title,state,isDraft,labels,author,baseRefName,headRefName,headRefOid,headRepositoryOwner,headRepository,mergeable,mergeStateStatus,reviewDecision,reviews,statusCheckRollup,latestReviews,files

Cache the JSON to /tmp/why-pr-<num>-<short-sha>.json. Pull headRefOid for any subsequent commits/<sha>/... calls.

Then fetch checks (one call):

gh pr checks <num> -R <owner>/<repo> --json name,state,conclusion,workflow,link

If a required commit status is not represented in the check rollup, use one more call:

gh api "repos/<owner>/<repo>/commits/<headRefOid>/status"

4. Run the CI-not-running diagnosis (--ci / --both)

Walk down this checklist in order. Stop at the first match per dimension; print all matches across the checklist.

#Symptom (from PR JSON / checks JSON)DiagnosisCite
C1PR isDraft == true AND a workflow has pull_request: types: [opened, synchronize, reopened] (default)Draft PRs do not fire pull_request events for ready_for_review excluded triggers. Mark the PR as ready or push a new commit.GitHub default pull_request event semantics
C2Workflow runs are present but jobs gated on needs: fork-approval are WAITING / pending environment approvalExternal fork PR: a merge/release-team member must approve the fork-ci environment for this run (GitHub UI → pending deployment). Each new push re-prompts.docs/ci/LABELS.md (fork-ci)
C3Same jobs SKIPPED after approval, AND qvac/fork-verified commit status is missing or not success for headRefOidfork-approval should record qvac/fork-verified on the head SHA after env approval via github.token (statuses: write). If missing, check fork-approval job logs. Self-hosted pull_request jobs (e.g. pr-test-inference-addon-cpp*) read this status, not labels.docs/ci/LABELS.md
C4PR is from a fork AND privileged jobs ran without env approval (should not happen post label-gate retirement)Report to DevOps — privileged fork jobs must needs: fork-approval.ci-trust-policy.test.mjs
C5PR is from a fork (headRepositoryOwner.login != tetherto) AND only secret-bearing jobs are missingExpected until fork-ci is approved for the current SHA. Unprivileged pull_request fork jobs stay read-only (no secrets).docs/ci/LABELS.md
C6An expensive validation workflow is missing AND PR is an external fork AND fork-ci not yet approved for current SHAAsk merge/release team to approve the pending fork-ci deployment on the latest workflow run. Do not recommend the retired verified label.docs/ci/LABELS.md
C7pr-checks-sdk-pod.yml jobs are skipped AND PR touches packages/sdk/ from a fork AND safe-to-test is missingSDK pod's check-running gate. Reviewer must apply safe-to-test after auditing the diff.LABELS.md § safe-to-test
C8E2E suite did not run AND PR touches SDK AND neither test-e2e-smoke nor test-e2e-full is presentSDK E2E is opt-in via these labels. Apply the smoke variant for normal PR feedback.LABELS.md § test-e2e-smoke / test-e2e-full
C9A workflow run is FAILED in fork-approval (red, not waiting)Hard misconfiguration — usually missing statuses: write on the gate job or failure recording qvac/fork-verified. DevOps issue.fork-approval job logs
C10Required check is in IN_PROGRESS state with no failure; user is just impatientWait. Or surface the slowest job's link.gh pr checks output

For each match, print what the rule says (one short quote pulled from the cite) plus what the user should do (a single concrete action).

5. Run the merge-blocked diagnosis (--merge / --both)

Walk this checklist in order, same rule: stop at first match per dimension, print all matches.

#SymptomDiagnosisCite
M1mergeable: "CONFLICTING" or mergeStateStatus: "DIRTY"Merge conflicts with <baseRefName>. Rebase or merge base into branch.gh pr view --json mergeable,mergeStateStatus
M2state: "CLOSED" or state: "MERGED"PR is not open. Re-open it (if closed) or there's nothing to merge (if merged).n/a
M3isDraft: trueDraft PRs cannot be merged. Mark ready for review.n/a
M4reviewDecision: "REVIEW_REQUIRED" AND CODEOWNERS approval not presentThe CODEOWNERS team(s) for the touched paths must approve. Identify the team via the file's owners line; suggest 1-2 names from the team JSON..github/CODEOWNERS, .github/teams/<pod>.json
M5reviewDecision: "CHANGES_REQUESTED"A review requested changes. Resolve the requested changes and either re-request review or have the reviewer dismiss.n/a
M6Check Approvals failed or its latest comment reports missing approvalsRead the workflow and bot output for the current deficit; do not infer historical tier rules..github/workflows/check-approvals.yml
M7A required check (per statusCheckRollup) is FAILEDThe required check must pass. Link to the failed run; if it's flake, re-run; if it's a real failure, fix.gh pr checks
M8A required check is missing entirely from statusCheckRollupEither the gating workflow is skipping/waiting (loop back to the CI section — usually fork-ci pending) OR a required check name in branch protection no longer matches a real job (DevOps issue).branch-protection ruleset
M9Base branch protection updated mid-PR (new required check added)Push an empty commit (git commit --allow-empty) to re-trigger checks against the new ruleset.n/a
M10All checks green, all approvals satisfied, mergeable: "MERGEABLE", mergeStateStatus: "CLEAN"Nothing is blocking. Print "ready to merge" and the merge command the user can run themselves (do not run it).n/a

When suggesting reviewers, use current CODEOWNERS and team files for the touched paths. Do not infer reviewers from an obsolete tier model.

6. Render the report

Print one consolidated report. Two top-level sections (omit a section if the user asked for a single dimension).

PR: <owner>/<repo>#<num> — <title>
Author: @<login>   Base: <baseRefName>   Head: <headRefName>@<short-sha>
State: <state> | Draft: <isDraft> | Mergeable: <mergeable>/<mergeStateStatus> | Review: <reviewDecision>
Labels: <comma-separated>

── CI: are checks running? ──────────────────────────────────────────
[<symbol>] <C#> <one-line summary>
   Rule:    <one-line quote from cited doc>
   Action:  <one concrete next step>
   Cite:    <relative link>

[<symbol>] <C#> ...

(or "✓ All expected checks are running.")

── Merge: can it land? ───────────────────────────────────────────────
[<symbol>] <M#> <one-line summary>
   Rule:    <one-line quote from cited doc>
   Action:  <one concrete next step>
   Cite:    <relative link>

[<symbol>] <M#> ...

(or "✓ All merge requirements satisfied.")

── Suggested next actions ───────────────────────────────────────────
1. <concrete action> — <user-friendly description>
2. ...

Use simple symbols: [!] for blocking, [~] for soft (e.g. waiting), [i] for informational, [✓] for satisfied. No emojis (per repo convention).

7. Plan-then-apply mutations (only if user opts in)

The skill MAY propose at most one mutation per finding. Each proposal prints the exact command and waits for explicit confirmation. Examples:

  • Apply safe-to-test (only after the user confirms they have audited the fork's diff):
    gh pr edit <num> -R <owner>/<repo> --add-label safe-to-test
    
  • Re-trigger approval bot:
    gh pr comment <num> -R <owner>/<repo> --body "/review"
    
  • Request re-review from a specific approver:
    gh api -X POST repos/<owner>/<repo>/pulls/<num>/requested_reviewers \
      -f reviewers='["<login>"]'
    
  • Mark draft PR as ready for review:
    gh pr ready <num> -R <owner>/<repo>
    

Never propose approving the fork-ci environment on the user's own PR. If privileged fork jobs are waiting, the suggestion is "ask a merge/release-team member to approve the pending fork-ci deployment" — not "I'll approve it for you."

After any mutation, re-run step 3 (single gh pr view) and re-render only the section(s) that changed. Print one verification line: Verified: <label X> now present | reviewers <Y, Z> requested | etc.

8. Stop conditions (fail-stop)

Stop and report (do not guess) when:

  • gh auth status reports unauthenticated → tell the user to run gh auth login.
  • The PR JSON returns 404 → wrong number / wrong repo / private repo.
  • The skill needs to read branch protection but the user lacks permission (HTTP 403) → state the limitation; the merge-blocked diagnosis falls back to "what we can see from PR state alone".
  • The diagnosis returns zero findings AND the user clearly believes something is broken → say so; offer to dump the raw PR JSON for the user to inspect.

Quality checklist

Before printing the final report, verify:

  • Each finding cites a real rule from docs/ci/LABELS.md, docs/ci/TEAMS.md, approval-check-worker.yml, or CODEOWNERS — not from memory.
  • Each finding has both a "Rule" (one-line quote) and an "Action" (one concrete step).
  • Suggested approvers come from current CODEOWNERS and team configuration, not memory.
  • No mutation has been executed without an explicit per-command confirmation.
  • fork-ci approval was never proposed for self-application.
  • Total gh shell calls ≤ 6 for a read-only diagnosis (≤ 8 if a mutation + verification was performed).

References

Signals

GitHub stars
601
Forks
111
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
qv-devops-why-my-pr-not
Source
github.com/tetherto/qvac