Skip to main content

ai-review-max-turns

A Claude-powered CI review check reports red. Use when triaging it, to separate a genuine finding the check could not publish from turn-budget exhaustion — read is_error first, never the red X.

설치로 이동

소스 정보

저장소
laurigates/claude-plugins
최근 소스 활동
2026년 9월 12일 18:54
감지된 SKILL.md 언어
영어
스타
58
포크
6

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
ai-review-max-turns
description
A Claude-powered CI review check reports red. Use when triaging it, to separate a genuine finding the check could not publish from turn-budget exhaustion — read is_error first, never the red X.
allowed-tools
Bash, Read, Grep, Glob, TodoWrite
created
2026-09-02T00:00:00.000Z
modified
2026-09-12T00:00:00.000Z
reviewed
2026-09-12T00:00:00.000Z
# A Red AI-Review CI Check Has Three Very Different Causes — Separate Them Before Acting A growing class of CI checks are **Claude-powered reviewers** — a workflow that runs the Claude Code action over the PR diff and reports findings as a pass/fail check. They are usually a family of `reusable-quality-*`, `reusable-security-*`, and `reusable-a11y-*` workflows, surfaced on the PR as checks named `typescript / analyze`, `secrets / scan`, `owasp / scan`, `aria / analyze`, `wcag / analyze`, and the like. They go red for three reasons that demand **opposite** responses. Reading one as another is the whole hazard: | Cause | Tell | Response | |---|---|---| | **Budget exhaustion** — the run died mid-flight | `error_max_turns` in the log; `is_error: true` | Ignore the failure; it says nothing about the code | | **Turn-ceiling overrun** — the run *finished*, the wrapper failed the job | `is_error: false`, `subtype: "success"`, **no** `Found N` line, and `##[error]Claude reported a successful result after N turns, exceeding the configured maximum of M` | Ignore the failure; the scan completed and found nothing | | **A real finding it could not publish** | `is_error: false`, `subtype: "success"`, a `::error::Found N …` line, **and no PR comment** | Investigate the code by hand — the check found something | > **The law: the red X is never the discriminator.** `is_error` separates a run > that *died* from one that *finished*, but it does not separate the two > finished cases — both read `is_error: false`, `subtype: "success"`. Between > those, the **finding count** decides: a `Found N` line means the check is > trying to tell you something through a blocked channel; its absence alongside > a turn-count error means the wrapper failed a scan that had nothing to say. Read `is_error` first, then the finding count. Stopping at `is_error` sends a turn-ceiling overrun to the hand-audit response, which is the same wasted investigation this skill exists to prevent. Read the subtype before forming any theory: ```sh url=$(gh pr checks <pr> -R <owner>/<repo> --json name,link \ --jq '.[]|select(.name=="<check>")|.link') runid=$(echo "$url" | sed -E 's#.*/runs/([0-9]+)/.*#\1#') gh run view "$runid" --log-failed 2>&1 | grep -iE '"is_error"|"subtype"|error_max_turns|num_turns' ``` ## Cause 1 — budget exhaustion (`error_max_turns`) On a large diff these jobs exhaust their per-run **turn budget** and fail with `subtype: error_max_turns` / `is_error: true` — a red ❌ that is infra flakiness, not a real finding. ### The tell: the failing set *rotates* across re-runs The defining signature — and the thing that distinguishes budget exhaustion from a genuine defect — is that **re-running the same commit fails a *different subset* of the AI jobs each time**: > Measured 2026-08 on a 16-file / ~1150-line PR, two runs of the *same* > commit: run 1 failed only `aria / analyze`; run 2 passed `aria` but > failed `typescript`, `secrets`, and `owasp`. All four logs showed > `error_max_turns` at `num_turns` 6–7. Deterministic gates (biome, knip, > conventional-commits, deps/audit, and the real `wcag / analyze`) passed > every run; the PR's full local test suite + build were green throughout. A real code defect fails the *same* check deterministically. A rotating failure set across re-runs is budget exhaustion — the scheduler gets through a different subset of the AI jobs before the turn cap each time. ### What to do (and not do) - **Do not blind-rerun.** A re-run re-trips with a *different* rotating subset — it never converges, and it just burns AI-action cost. One rerun to observe the rotation is enough to diagnose; after that, stop. - **Do not chase the "finding."** There is none — the job died before finishing. Reading the partial log for "what it flagged" is wasted effort. - **Check whether it actually blocks — read `mergeStateStatus`, don't assume.** `gh pr view <n> --json mergeable,mergeStateStatus`: `UNSTABLE` means the failing check is present but **not required**, so a plain `gh pr merge` works; `BLOCKED` means it is required and the merge is refused. Where it is `UNSTABLE`, merge on the strength of the deterministic gates + local verification (see `git-plugin:git-merge-hazards` for the two checks a merge-over-red needs). - **Fix the root cause upstream, once.** The budget is too low for large diffs. Raise `max_turns` on the reusable workflow (or expose it as an input and bump callers — `reusable-claude.yml` already defaults to 30), narrow `file-patterns`, gate on `max-diff-lines`, or have `error_max_turns` post a neutral continuation status instead of a hard fail. Tracked in `ForumViriumHelsinki/.github#79`. ## Cause 2 — turn-ceiling overrun on a run that succeeded Distinct from Cause 1: nothing died. The model returned a normal successful result, and the *action wrapper* then failed the job because the turn count exceeded `--max-turns`. The scan's own verdict is discarded along with it. ```sh gh run view --job <job-id> -R <o>/<r> --log | grep -E '"is_error"|"subtype"|"num_turns"|exceeding the configured maximum' ``` ``` "subtype": "success", "is_error": false, "num_turns": 53, ##[error]Claude reported a successful result after 53 turns, exceeding the configured maximum of 50 ``` Nondeterministic in exactly the way Cause 1 is — same check, same tree, different turn count. Do not read a pass on the next run as evidence a change fixed anything. > Evidence (2026-08-28, pal-mcp-server#87): `secrets-scan / scan` passed, then > failed after a rebase that changed no scanned content, at `num_turns: 53` > against a max of 50 with `permission_denials_count: 6` and no finding. It > passed again on the next push. The diff was comment-only edits to > `.env.example`; the scan had nothing to report either time. The `permission_denials_count` interaction from Cause 3 applies here too: denied tool calls get retried, and the retries are what push a scan over the ceiling. So a high denial count is a cause of this failure, not a signal about the code. **Fix it upstream**, not in your PR — raise `--max-turns`, or grant the tool the scan keeps being denied. Re-running just re-rolls the count. ## Cause 3 — a real finding the check cannot publish These workflows are told `Leave a PR comment with findings` but are granted no comment tool, so every attempt is denied and the prose is discarded — while `fail-on-critical` still fails the build off the count in `structured_output`. The result is a gate that **blocks a merge on a finding nobody can read.** ```sh gh run view --job <job-id> --log | grep -E '"is_error"|"subtype"|permission_denials_count|critical security' gh pr view <n> --json comments --jq '.comments | length' # 0 = it could not publish gh api repos/<o>/<r>/check-runs/<job-id> --jq .output # all null = nothing to read ``` `permission_denials_count` in double digits is the signature: the model retries the blocked call, which also burns budget. **Do not treat an unreadable finding as a false positive.** Read the analyzed files yourself against the check's own category list. The finding is often in *pre-existing* code the scan read alongside the diff — but not always. > Evidence (2026-08, pal-mcp-server#76): three `owasp / scan` runs reported > 1, then **2**, then 1 criticals — the middle one on a byte-identical commit — > with 8/6/13 denials, $5.44 total, and zero comments. The finding was real: > `estimate_file_tokens` stat'd caller-supplied paths with no validation, and a > change in that same PR had just started reporting per-file sizes in the > rejection — turning it into an existence-and-size oracle for the files > `is_dangerous_path` protects (`/etc/passwd` read back as 2,669 tokens). > Found only by reading the code. Fixed in laurigates/.github#47/#48. **The count is not stable.** Same commit, different answer. Never treat a delta between runs as evidence a fix worked. ## The trap under all three: a check that never ran looks exactly like a pass Before using a sibling PR as a "it's green there" control, check the **duration**. These workflows carry `file-patterns` filters, so a PR touching no matching files completes in **4–6 seconds** having analyzed nothing — and reports `pass`. ```sh gh pr checks <n> | grep -E "owasp|code-smell" # 4s pass = skipped, not clean gh run list --workflow security.yml --branch main -L 5 # empty = PR-triggered only, no main baseline ``` A green tick from a skipped run is not a control. This is `never-fabricate-test-identifiers.md`'s known-good control applied to CI: if the "passing" comparison never executed, you have no baseline, and `pr-merge-hazards.md`'s merge-over-red test ("same check already fails on `main`") cannot be satisfied. ## When it bites - Any PR large enough that a per-file AI reviewer can't finish in its turn budget — refactors, new-feature slices, multi-file guards (the rotating-failure example above was 16 files). - Repos that later mark these AI checks **required** — there, the flake *does* wedge the merge, which makes raising `max_turns` urgent rather than cosmetic. - Any security-category scan whose red you are tempted to wave through on the strength of green deterministic gates. Cause 3 looks identical from the outside and is exactly where that reflex is most expensive. ## Rationale A red check on valid code is worse than no check: it reads as a real finding, so it pulls a reviewer into chasing a non-existent defect and erodes trust in the AI-review signal. But the inverse error is worse still — treating every AI-review red as flakiness waves through the findings that are real and merely unpublishable. One `grep` for `is_error` and the finding count separates all three. Same instinct as `github-actions-plugin:multirepo-ci-cd`: diagnose against what CI actually did (read the run), not against the surface red. ## Related - `.claude/rules/pr-merge-hazards.md` §4 — `UNSTABLE` vs `BLOCKED`, and the two checks required before merging over red - `laurigates/.claude/rules/ci-cd-workflows.md` — the three *green*-but-inert modes of these same workflows (empty secret, workflow anti-tampering, skipped by filter); this skill covers the *red* modes - `~/.claude/rules/diagnose-at-the-failure-point.md` — measure at the failure point rather than accepting the framing the error hands you - `github-actions-plugin:multirepo-ci-cd` — portfolio-wide CI diagnosis discipline
GitHub에서 보기