Skip to main content

ci-diagnose

Diagnose a CI failure now — fetch the failed run, redact and match its logs against the F01-F12 pattern library, and report root cause with fixes. Use when a GitHub Actions run has failed and you want its root cause and a fix now (for the reference workflow guide, use the diagnose-ci skill).

Aller à l'installation

Informations de source

Dépôt
KingInYellows/yellow-plugins
Dernière activité de la source
6 septembre 2026 à 22:45
Langue détectée de SKILL.md
anglais
Étoiles
0
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
ci-diagnose
description
Diagnose a CI failure now — fetch the failed run, redact and match its logs against the F01-F12 pattern library, and report root cause with fixes. Use when a GitHub Actions run has failed and you want its root cause and a fix now (for the reference workflow guide, use the diagnose-ci skill).
user-invocable
false
## What It Does Actively diagnoses a failed GitHub Actions run: validates prerequisites, resolves the run, fetches the failed logs, **redacts secrets and fences the content**, matches it against the F01-F12 failure-pattern library, and reports the root cause with actionable fixes. This is the *run diagnosis now* skill; the `diagnose-ci` skill is the reference workflow guide, not an executable diagnosis. ## When to Use - A CI run failed and you want the root cause and a fix right now. - The user asks to "diagnose the build", "why did CI fail?", or "what broke?". ## Usage The argument text after the skill name may contain a run ID (digits only) and an optional `--repo owner/name` override. With no run ID, the latest failed run is diagnosed. ### Step 1: Validate Prerequisites Check GitHub CLI authentication: ```bash gh auth status 2>&1 | head -n 3 ``` If not authenticated: "GitHub CLI not authenticated. Run: `gh auth login`". **Parse `--repo` first.** If the argument text after the skill name contains `--repo owner/name`, extract it into `REPO_OVERRIDE` and validate the format now (exactly one `/`, alphanumeric plus hyphens, dots, and underscores); report the format error and stop if it is invalid. An explicit override is a complete repository context on its own, so when `REPO_OVERRIDE` is set, **skip the origin-remote detection below entirely** and proceed to Step 2 — otherwise the advertised override could never be used from outside a GitHub checkout, which is exactly when it is most useful. When no override was given, check repository context — resolve the origin remote explicitly, accept only `github.com` remotes (SCP-like SSH, `ssh://`, or HTTPS), and fail closed to `NO_REMOTE` on any command failure or non-GitHub host: ```bash REMOTE_URL=$(git remote get-url origin 2>/dev/null) GIT_REMOTE_STATUS=$? if [ "$GIT_REMOTE_STATUS" -ne 0 ] || [ -z "$REMOTE_URL" ]; then REPO_CONTEXT="NO_REMOTE" else REPO_CONTEXT=$(printf '%s\n' "$REMOTE_URL" \ | grep -oE '^(git@github\.com:|https://github\.com/|ssh://git@github\.com(:[1-9][0-9]{0,4})?/)[^/]+/[^/]+$' \ | sed -E 's#^(git@github\.com:|https://github\.com/|ssh://git@github\.com(:[1-9][0-9]{0,4})?/)##; s/\.git$//') [ -z "$REPO_CONTEXT" ] && REPO_CONTEXT="NO_REMOTE" fi # This block's own subprocess ends here, so the decision below must be made # now, in-block — a later block could not read $REPO_CONTEXT at all. if [ "$REPO_CONTEXT" = "NO_REMOTE" ]; then echo "Not in a Git repository with a GitHub remote. Navigate to your project root, or pass --repo owner/name." exit 1 fi ``` Stderr from `git remote get-url` is discarded (`2>/dev/null`), not piped into the parser — an error message must never be mistaken for a repo slug. A failed command or an empty URL yields `NO_REMOTE` directly. Any URL that isn't a `github.com` SCP-like SSH (`git@github.com:owner/repo(.git)`), full `ssh://` (`ssh://git@github.com/owner/repo(.git)`, optionally with a port — `ssh://git@github.com:2222/owner/repo(.git)`), or HTTPS (`https://github.com/owner/repo(.git)`) remote — including other hosts such as GitLab, or a suffix-confusable host like `github.com.evil.com` — falls through to `NO_REMOTE` as well. The host segment is matched as a literal `github\.com` immediately followed by `:`, `/`, or an optional `:PORT/`, so a lookalike host with `github.com` as a prefix never satisfies the pattern. Bare `ssh://github.com/...` (no `git@` userinfo) and the legacy `git://` protocol are intentionally out of scope: GitHub requires the `git` user for SSH, and it disabled the unauthenticated `git://` protocol in 2021, so neither form is a legitimate remote to accept here. The block above already reports that message and stops (`exit 1`) when `$REPO_CONTEXT` resolves to `NO_REMOTE` — this block only ever runs when no `--repo` override was given (Step 1 skips it entirely otherwise), so the message and the "no override" condition are one and the same check, made in-block rather than deferred to a later step that could not read `$REPO_CONTEXT` anyway. ### Step 2: Resolve Run ID `REPO_OVERRIDE` was parsed from the argument text by the model in Step 1 (that parsing gated the origin-remote check there), but Step 1 has no bash block that assigns it — it was never bound as an actual shell variable, and even if it had been, that binding would not survive into a later block's fresh subprocess (see `docs/solutions/code-quality/bash-block-subshell-isolation-in-command-files.md`). Every executable block below that builds `REPO_ARGS` must therefore embed the already-validated value as a literal itself — `REPO_OVERRIDE="owner/name"`, or an empty string if none was given — the same technique `RUN_ID` uses when 4a re-establishes it from Step 2's printed output. `REPO_ARGS` is then built from it — reused by every `gh run list`/`gh run view` call below and in Step 4a — so each honors the override instead of the detected origin repo: ```bash # Illustrative shape only — not run standalone. Each executable block below # embeds this same pattern with REPO_OVERRIDE set to a literal, since none of # them can inherit Step 1's parsing. REPO_OVERRIDE="<the --repo value parsed in Step 1, or empty string if none>" if [ -n "$REPO_OVERRIDE" ]; then REPO_ARGS=(--repo "$REPO_OVERRIDE") else REPO_ARGS=() fi ``` `REPO_ARGS` is empty when no override was given, so `"${REPO_ARGS[@]}"` expands to nothing and each `gh` call falls back to `gh`'s own repo detection from the current directory. `REPO_ARGS` is a pure function of `REPO_OVERRIDE` (itself just read from the argument text, no command execution) so re-embedding the literal and rebuilding `REPO_ARGS` from it is cheap and safe to repeat verbatim in every block below — unlike `RUN_ID`, which must not be rebuilt (see Step 3). Both branches below must leave `RUN_ID` bound to a value that has passed `^[1-9][0-9]{0,19}$` validation before it is ever passed to `gh run view`. **Resolving `RUN_ID` and fetching its run details (Step 3) must run as a single Bash tool invocation.** Each fenced snippet is a fresh subprocess — a value assigned by command substitution in one is gone in the next (see `docs/solutions/code-quality/bash-block-subshell-isolation-in-command-files.md`). Capturing `RUN_ID` here and reading it from a separate Step 3 block would leave `$RUN_ID` unbound when `gh run view` runs, regardless of how carefully the capture itself is validated. The two paths below are therefore each shown combined with the Step 3 fetch, not as a standalone block. **Explicit run ID.** If the argument text after the skill name contains a run ID (digits only), validate it against `^[1-9][0-9]{0,19}$` (no leading zeros, max 9007199254740991), assign it to `RUN_ID`, and continue into the SAME invocation as Step 3's fetch: ```bash RUN_ID="<digits parsed from the argument text>" if ! printf '%s' "$RUN_ID" | grep -qE '^[1-9][0-9]{0,19}$'; then echo "Invalid run ID. Must be a positive integer (e.g., 123456789)" exit 1 fi REPO_OVERRIDE="<the --repo value parsed in Step 1, or empty string if none>" if [ -n "$REPO_OVERRIDE" ]; then REPO_ARGS=(--repo "$REPO_OVERRIDE") else REPO_ARGS=() fi # Step 3, same invocation: RUN_ID is bound and validated above. RUN_DETAILS=$(gh run view "$RUN_ID" --json status,conclusion,jobs,headBranch,displayTitle,url,createdAt "${REPO_ARGS[@]}" 2>&1) DETAILS_STATUS=$? if [ "$DETAILS_STATUS" -ne 0 ]; then echo "Could not fetch details for run $RUN_ID (gh exited $DETAILS_STATUS). Not diagnosing." exit 1 fi # Portability gate — GNU sed required: displayTitle, headBranch, and job/step # names are attacker-controllable and go through the same redaction pipeline # as log content below (4a), which relies on GNU-only sed (`\x01`, `\|` BRE # alternation, the `I` flag). A non-GNU sed silently fails to match these # instead of erroring, so detect it here (mirroring 4a's gate) and refuse # rather than risk displaying an unredacted credential. if sed --version </dev/null 2>/dev/null | grep -q 'GNU sed'; then SED_CMD=sed elif command -v gsed >/dev/null 2>&1 && gsed --version </dev/null 2>/dev/null | grep -q 'GNU sed'; then SED_CMD=gsed else echo "Run-detail sanitization requires GNU sed; found only a non-GNU 'sed' and no 'gsed' on PATH. Install GNU sed (macOS: brew install gnu-sed) and retry. Refusing to display unredacted run metadata." exit 1 fi # Redact secrets in run metadata BEFORE fence-escaping — same 13+-pattern # pipeline 4a applies to log content (see 4a's comment for the # PROTECT/RESTORE provenance rationale; mirrors hooks/scripts/lib/redact.sh). # displayTitle, headBranch, and job/step names are user-authored strings that # can carry an accidentally-committed credential as easily as a log line can. SAFE_DETAILS=$( set -o pipefail printf '%s\n' "$RUN_DETAILS" | "$SED_CMD" \ -e 's/\x01/?/g' \ -e 's/ghp_[A-Za-z0-9_]\{36,255\}/\x01REDACTED:github-token]/g' \ -e 's/ghs_[A-Za-z0-9_]\{36,255\}/\x01REDACTED:github-token]/g' \ -e 's/gho_[A-Za-z0-9_]\{36,255\}/\x01REDACTED:github-token]/g' \ -e 's/ghr_[A-Za-z0-9_]\{36,255\}/\x01REDACTED:github-token]/g' \ -e 's/ghu_[A-Za-z0-9_]\{36,255\}/\x01REDACTED:github-token]/g' \ -e 's/github_pat_[A-Za-z0-9_]\{22,255\}/\x01REDACTED:github-pat]/g' \ -e 's/AKIA[0-9A-Z]\{16\}/\x01REDACTED:aws-access-key]/g' \ -e 's/\(aws_secret_access_key\|AWS_SECRET_ACCESS_KEY\)[[:space:]]*[=:][[:space:]]*[A-Za-z0-9/+=]\{40,\}/\1=\x01REDACTED:aws-secret]/gI' \ -e 's/\(\(Authorization\|Proxy-Authorization\)[[:space:]]*:[[:space:]]*[A-Za-z][A-Za-z0-9_-]*\)[[:space:]]\+[^\x01[:space:]]\+\([[:space:]]\+[A-Za-z0-9_-]\+=[^\x01[:space:]]\+\)*/\1 \x01REDACTED]/gI' \ -e 's/\(\(Authorization\|Proxy-Authorization\)[[:space:]]*:[[:space:]]*\)[^\x01[:space:]]\+[[:space:]]*$/\1\x01REDACTED]/gI' \ -e 's/Bearer[[:space:]]\+[A-Za-z0-9._-]\{20,\}/Bearer [REDACTED]/g' \ -e 's/dckr_pat_[A-Za-z0-9_-]\{32,\}/\x01REDACTED:docker-token]/g' \ -e 's/npm_[A-Za-z0-9]\{36\}/\x01REDACTED:npm-token]/g' \ -e 's/pypi-[A-Za-z0-9_-]\{32,\}/\x01REDACTED:pypi-token]/g' \ -e 's/eyJ[A-Za-z0-9_-]\{10,500\}\.eyJ[A-Za-z0-9_-]\{10,500\}\.[A-Za-z0-9_-]\{10,500\}/\x01REDACTED:jwt]/g' \ -e 's/\(password\|passwd\|pwd\|secret\|token\|api_key\|apikey\|api-key\|auth\|credential\|private_key\|privatekey\|private-key\)[[:space:]]*[=:][[:space:]]*"\(\\.\|[^"\\]\)*"/\1=\x01REDACTED:quoted]/gI' \ -e "s/\(password\|passwd\|pwd\|secret\|token\|api_key\|apikey\|api-key\|auth\|credential\|private_key\|privatekey\|private-key\)[[:space:]]*[=:][[:space:]]*'\(\\\\.\\|[^'\\\\]\)*'/\1=\x01REDACTED:quoted]/gI" \ -e 's/\(-\{1,2\}\)\(password\|passwd\|pwd\|secret\|token\|api_key\|apikey\|api-key\|auth\|credential\|private_key\|privatekey\|private-key\)[[:space:]]\+"\(\\.\|[^"\\]\)*"/\1\2=\x01REDACTED:quoted]/gI' \
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub