Workflow Health Triage
Implements spec/project/workflow-health/ §Triage before remediation and §Specialised-agent dispatch for remediation. The skill is the generalist that classifies a failure and dispatches the hands-on fix to the most specialised available Claude agent; it never performs the fix itself when a matching specialised agent exists.
Why this is a skill, not an agent
- Externally-visible mutations gate on user confirmation. Classification ambiguity, agent-dispatch confirmation, and the fix-PR title / body are mid-flow user dialogues; an agent's fire-and-forget shape would miss them.
- Orchestrator pattern (per
skill-vs-agent). The work itself is classify, dispatch, verify; the dispatched specialised agent does the editing. The orchestrator stays in the main thread and chains other skills (pull-request-create for the fix PR, optionally pull-request-merge after CI is green).
- Per-classification user gating. At least three of the six classes (
defect, flake, secret drift) need a human "yes that's the right call" before the dispatcher commits to a remediation lane.
- Counter-dimension considered: a narrow agent could handle the classification step in isolation and gain context-window protection, but every downstream lane (PR creation, agent dispatch, verification) is interactive—keeping the whole flow in one skill is simpler than splitting at the classification boundary.
User-language policy
Detect the user's language and respond in it. All git, gh, and Agent(subagent_type=…) invocations stay English so the audit trail (PR titles, classification labels, agent names) is grep-able portfolio-wide.
Preconditions
Before any classification:
- Confirm the working directory is a git repository and
gh auth status reports authenticated.
- Confirm
spec/project/workflow-health/<canonical_language>.md is reachable — in the current project or, when absent there, at ${CLAUDE_PLUGIN_ROOT}/spec/project/workflow-health/<canonical_language>.md (the copy shipped inside the installed nolte-shared plugin). If neither is reachable, stop and report—without it the classifications are ad-hoc; this skill is the spec's implementer, not its replacement.
- The user supplies the failing workflow run (a
gh run view <id> URL, a workflow file name like release-publish.yml, or "the red one on develop"). If nothing is supplied, run gh run list --status failure --branch develop --limit 5 and ask which run to triage.
Operations
1. Inspect the failing run
Run in parallel:
gh run view <id> --json name,headBranch,headSha,event,status,conclusion,workflowName,url,jobs
gh api repos/<owner>/<repo>/actions/runs/<id>/jobs --jq '.jobs[] | {name, conclusion, html_url}'
gh run view <id> --log-failed (capture the failing-step output for classification)
git log --oneline -1 <headSha> (resolve the commit under the run)
Tooling (optional GitHub MCP): the GitHub reads above are Actions / workflow-run
reads. When a connected GitHub MCP server exposes an actions read toolset, prefer it
and verify the exact tool names against the pinned server version, per
spec/claude/mcp-tool-preference/; the reference hosted endpoint exposes no actions
toolset, so these reads stay on gh (a documented OQ-D coverage gap). gh stays
authoritative and the fallback is never removed.
Confirm the run is on develop or main (the spec's scope) and is conclusion: failure. If it's still in_progress, stop and ask the user to wait for completion before triage; if it's cancelled, classify as other with a one-line note and stop.
2. Classify before any re-run
Walk the table top-down and stop at the first match. The rows are ordered by specificity rather than by the spec's class order, so a narrow signal is tested before the broader rows that would also capture it; every row still resolves to one of the spec's six classes:
| Signal in the failed-step output |
Classification |
Push rejected with refusing to allow a GitHub App to create or update workflow ... without 'workflows' permission |
infra — evaluate this row first. Both the defect row (the rejection names a workflow file the diff modified) and the secret drift row (it is a 403) would otherwise capture it, and neither is right: it is a deterministic platform constraint, not a repository defect and not an expired credential |
| Failing step references a file the head commit's diff modified |
defect |
Re-run of the same headSha would produce green (no infra signal, no code change in the failing step's surface) |
flake |
| HTTP 5xx, rate-limit, registry-unreachable, GitHub status incident |
infra |
uses: pin in the workflow points to a nolte/gh-plumbing (or other reusable) tag, and a newer tag exists with the relevant fix |
stale pin |
Token, deploy key, or OIDC trust expired or rotated; failure references 401, 403, expired, unauthorized |
secret drift |
| None of the above |
other (with a short note explaining why) |
Confirm the chosen class with the user before proceeding—at minimum for defect, flake, and secret drift, where misclassification has the highest cost. Apply the classification by writing it to a scratch note that becomes the seed of the fix PR's Risk / rollout notes.
Hard: never call gh run rerun <id> more than once before a recorded classification exists; repeated blind re-runs are drift per spec/project/workflow-health/ §Triage before remediation.
3. Dispatch the most specialised available agent
The set of available agents changes over time; never freeze a snapshot of "which agents exist" inside this skill body. Resolve the dispatch target dynamically each invocation:
- Resolve the candidate set.
Glob agents/*.md (plus .claude/agents/*.md (project-distributed; additionally ~/.claude/agents/*.md for user-level agents) for the project-distributed half), then Read the description: line of every candidate. Build a (name, description) table; the table is the runtime inventory.
- Match classification to candidate. Walk the candidates and pick the one whose
description most closely matches the (classification, failing-artefact-area) pair: a defect in a markdown spec/skill/agent file maps to whichever agent's description names "spec-conformant authoring" or the equivalent; a defect in a workflow YAML maps to whichever agent's description names "workflow YAML" or "GitHub Actions"; a documentation defect maps to whichever agent names an audience-targeted documentation responsibility; and so on. The match is on the description's stated responsibility, not on the agent's name.
- Recognise the no-fix classifications.
flake and secret drift produce no agent dispatch by design — the work is documenting the flake in the project's flake registry (FLAKES.md or the flake-labelled issue set, whichever the repository uses) for flake, or rotating the credential outside Claude for secret drift. The skill produces only the fix PR that re-references the rotated credential.
- No match is a portfolio gap. When the candidate walk produces no plausible match and the failure class has occurred three or more times historically (use
gh run list --status failure --branch develop --limit 50 plus a quick grep to estimate), surface this as a portfolio gap per spec/project/workflow-health/ §Specialised-agent dispatch — the user is asked whether to author a new agent (via claude-plugin-developer) before the fix PR opens. When a match exists, dispatch with Agent(subagent_type="<plugin>:<agent>") and pass the classification, the run URL, the failing-step excerpt, and the fix-PR-title hint. The classification is a hypothesis about the cause, so the dispatch brief MUST authorise refutation per spec/claude/dispatch-brief/: tell the agent it may refute the classification, and treat a returned refutation (contradicting evidence plus what it did instead) as a first-class result — re-classify rather than force the fix — not a failed dispatch. Wait for the agent's report.
The dynamic-lookup design means a new specialised agent that lands in agents/ becomes dispatchable immediately, without a coordinated edit to this skill — and a renamed or removed agent stops being a target the next time the skill runs, with no stale snapshot to mislead the dispatch.
4. Verify the fix PR carries the audit trail
Whether the editing was done by a specialised agent or the generalist, the resulting fix PR MUST carry both pieces of evidence in its Risk / rollout notes section per the pull-request-workflow spec:
- Triage classification verbatim (one of the six labels above, with a one-line note for
other)
- Dispatched agent name (the literal
subagent_type argument), or the literal phrase "no matching specialised agent—generalist remediation"
If the user wants the skill to open the fix PR itself, dispatch pull-request-create with these two lines pre-populated in the Risk / rollout notes; the user still confirms the title and body before push, per the pull-request-create spec's externally-visible-action gate.
5. Verify the standard PR gate
After the fix PR opens, the skill stops. The actual merge belongs to pull-request-merge, which re-validates the gate. The skill MUST NOT:
- merge the fix PR itself (out of scope;
pull-request-merge owns that)
- pass
--admin anywhere (enforce_admins: true on develop has no exception)
- waive a still-failing required check on the fix PR (the spec forbids
continue-on-error masking and required-check removal)
Report back the run ID, the classification, the dispatched agent name (or "generalist"), the fix-PR URL, and a one-line "next action: invoke pull-request-merge after CI is green".
Old patterns
Earlier revisions of this skill enumerated specific agent names inline (for example workflow-yaml-fixer, claude-plugin-developer, audience-doc-author) as the dispatch table. That snapshot rotted whenever a new agent landed or an existing one renamed; the runtime-Glob design in Operation 3 replaces it. The historical mapping is preserved here only so a reader who recognises the prior wording can spot the transition: defect in workflow YAML used to fall back to generalist (no matching agent), defect in spec / skill / agent files used to dispatch claude-plugin-developer, defect in documentation used to dispatch audience-doc-author. Use the runtime lookup in Operation 3 instead of this snapshot.
Examples
- Read
examples/01-defect-classification-dispatch.md when triaging a failure that classifies as defect and dispatches to a specialised agent.
- Read
examples/02-stale-pin-portfolio-gap.md when the failure root-causes to a stale pin in the portfolio plumbing.
- Read
examples/03-flake-no-fix-record-only.md when the failure classifies as flake and the skill records the classification without opening a fix PR.
Resumability
Per spec/claude/resumable-work/, this skill is resumable: true. State is persisted to .resume/workflow-health-triage/<run-id>.yml after every successful user-approval gate and after each named phase boundary. On re-invocation, scan that directory for files with status: in_progress whose inputs: snapshot matches the current invocation; if one matches, prompt the operator with Resume run <run_id> from phase <phase> (last checkpoint <last_checkpoint_at>)? [resume / start-new / discard]. The state-file envelope (schema_version, run_id, inputs, phase, decisions[], status, ...) and the fail-closed semantics on schema or YAML errors are load-bearing in the spec; don't duplicate those rules here.
Hard rules
- Never re-run a failed required workflow run more than once before a recorded triage classification exists; the spec calls repeated blind re-runs drift.
- Never classify a failure as
flake without reproducible evidence (re-run of the same headSha returned green and no infra signal explains the first failure).
- Never mask a failure: don't propose
continue-on-error: true on a required job, don't propose removing a check from the required-checks set in .github/settings.yml without a tracking Issue, don't propose repointing a nolte/gh-plumbing pin from a tag to a branch.
- Never bypass branch protection on the fix PR;
enforce_admins: true on develop has no exception path.
- Never dispatch a specialised agent with a classification the spec doesn't list. The closed set is
defect / flake / infra / stale pin / secret drift / other.
- Never open a fix PR whose Risk / rollout notes don't carry the classification and the dispatched agent name (or the explicit "no matching specialised agent" note).
- Always prefer a plugin-distributed specialised agent over the generalist when one matches; the spec's §Specialised-agent dispatch makes this a SHOULD that this skill upgrades to a hard contract for the dispatch step.
- When
spec/project/workflow-health/ disagrees with this skill, the spec wins. Propose updating this skill rather than silently diverging.
Gotchas
Per spec/claude/skill-management/ §Gotchas—concrete corrections to non-obvious environment facts the executing agent would otherwise get wrong.
GITHUB_TOKEN-cascade failures aren't defect. A release-drafter.yml that doesn't fire after an automerge.yaml squash-merge, or a release-cd-refresh-master.yml that doesn't fire after release-publish.yml, is the documented infra class per spec/project/workflow-health/ §Known platform constraints—and the remediation is upstream in nolte/gh-plumbing, not in the consumer repo. Don't open a fix PR against the consumer's workflow YAML; document the infra classification and reference the nolte/gh-plumbing tracking Issue.
- A workflow-file push rejection reads like
secret drift and isn't. refusing to allow a GitHub App to create or update workflow ... without 'workflows' permission is a 403-flavoured rejection, so the secret drift row matches it on the surface. It's the second constraint in spec/project/workflow-health/ §Known platform constraints: GITHUB_TOKEN is a GitHub App identity and can never write under .github/workflows/, and the permissions: block has no workflows scope to grant. Rotating a credential fixes nothing. Classify infra and route to the App-token remediation, whose installation needs workflows: write on top of contents: write. It surfaces intermittently, only when the pushed diff happens to touch a workflow file, so a green history doesn't rule it out.
pascalgn/automerge-action exits 0 on mergeResult: 'merge_failed'. A automerge.yaml run with conclusion success whose log carries mergeResult: 'merge_failed' or Failed to merge PR: is a stale pin failure (the reusable's MERGE_METHOD default doesn't match the repo's allowed strategy in some pre-fix versions). Triage the automerge.yaml uses: tag, not the workflow YAML itself.
- Renovate-generated bump PRs for
nolte/gh-plumbing aren't automerged in this portfolio. A stale pin remediation that proposes to enable Renovate automerge for nolte/gh-plumbing violates workflow-health §Upstream drift and the AC against it. The remediation is a human-acknowledged Renovate PR, not an automerge rule.
flake without reproducible evidence is defect. A "let's just re-run and hope" reflex is exactly what the spec forbids. If the same headSha doesn't re-run cleanly green and no infra signal explains the first failure, the class is defect and the work is a fix, not a tracking entry.
Multi-model testing
Examples and operations in this skill are verified on Claude Sonnet as the default model; spot-checked on Haiku for cost-sensitive runs; Opus is appropriate for high-stakes audits that require deeper reasoning. The skill body has no model-specific assumptions beyond standard tool-call semantics.
1---2name: workflow-health-triage3description: Triages a failing GitHub Actions workflow run on `develop` or `main` per `spec/project/workflow-health/`. Classifies the failure into one of `defect` / `flake` / `infra` / `stale pin` / `secret drift` / `other`, dispatches the most specialised Claude agent that matches the classification, records the classification plus the dispatched agent's name in the eventual fix PR's Risk / rollout notes, and verifies the standard `fix/`-PR flow. Invoke when the user asks to "triage this red workflow", "classify this CI failure", or equivalent German-language requests. Don't use to silence checks via `continue-on-error` shortcuts or by removing required-checks entries (forbidden by spec); don't use to bypass branch protection (`enforce_admins` on develop has no exception path); don't use to merge a fix PR (use `pull-request-merge`). Supports resume on re-invocation per `spec/claude/resumable-work/`.4---56# Workflow Health Triage78Implements `spec/project/workflow-health/` §Triage before remediation and §Specialised-agent dispatch for remediation. The skill is the generalist that classifies a failure and dispatches the hands-on fix to the most specialised available Claude agent; it never performs the fix itself when a matching specialised agent exists.910## Why this is a skill, not an agent1112- **Externally-visible mutations gate on user confirmation.** Classification ambiguity, agent-dispatch confirmation, and the fix-PR title / body are mid-flow user dialogues; an agent's fire-and-forget shape would miss them.13- **Orchestrator pattern (per `skill-vs-agent`).** The work itself is *classify, dispatch, verify*; the dispatched specialised agent does the editing. The orchestrator stays in the main thread and chains other skills (`pull-request-create` for the fix PR, optionally `pull-request-merge` after CI is green).14- **Per-classification user gating.** At least three of the six classes (`defect`, `flake`, `secret drift`) need a human "yes that's the right call" before the dispatcher commits to a remediation lane.15- Counter-dimension considered: a narrow agent could handle the classification step in isolation and gain context-window protection, but every downstream lane (PR creation, agent dispatch, verification) is interactive—keeping the whole flow in one skill is simpler than splitting at the classification boundary.1617## User-language policy1819Detect the user's language and respond in it. All `git`, `gh`, and `Agent(subagent_type=…)` invocations stay English so the audit trail (PR titles, classification labels, agent names) is grep-able portfolio-wide.2021## Preconditions2223Before any classification:2425- Confirm the working directory is a git repository and `gh auth status` reports authenticated.26- Confirm `spec/project/workflow-health/<canonical_language>.md` is reachable — in the current project or, when absent there, at `${CLAUDE_PLUGIN_ROOT}/spec/project/workflow-health/<canonical_language>.md` (the copy shipped inside the installed `nolte-shared` plugin). If neither is reachable, stop and report—without it the classifications are ad-hoc; this skill is the spec's implementer, not its replacement.27- The user supplies the failing workflow run (a `gh run view <id>` URL, a workflow file name like `release-publish.yml`, or "the red one on develop"). If nothing is supplied, run `gh run list --status failure --branch develop --limit 5` and ask which run to triage.2829## Operations3031### 1. Inspect the failing run3233Run in parallel:3435- `gh run view <id> --json name,headBranch,headSha,event,status,conclusion,workflowName,url,jobs`36- `gh api repos/<owner>/<repo>/actions/runs/<id>/jobs --jq '.jobs[] | {name, conclusion, html_url}'`37- `gh run view <id> --log-failed` (capture the failing-step output for classification)38- `git log --oneline -1 <headSha>` (resolve the commit under the run)3940**Tooling (optional GitHub MCP):** the GitHub reads above are Actions / workflow-run41reads. When a connected GitHub MCP server exposes an actions read toolset, prefer it42and verify the exact tool names against the pinned server version, per43`spec/claude/mcp-tool-preference/`; the reference hosted endpoint exposes no actions44toolset, so these reads stay on `gh` (a documented OQ-D coverage gap). `gh` stays45authoritative and the fallback is never removed.4647Confirm the run is on `develop` or `main` (the spec's scope) and is `conclusion: failure`. If it's still `in_progress`, stop and ask the user to wait for completion before triage; if it's `cancelled`, classify as `other` with a one-line note and stop.4849### 2. Classify before any re-run5051Walk the table top-down and stop at the first match. The rows are ordered by specificity rather than by the spec's class order, so a narrow signal is tested before the broader rows that would also capture it; every row still resolves to one of the spec's six classes:5253| Signal in the failed-step output | Classification |54|---|---|55| Push rejected with `refusing to allow a GitHub App to create or update workflow ... without 'workflows' permission` | `infra` — **evaluate this row first.** Both the `defect` row (the rejection names a workflow file the diff modified) and the `secret drift` row (it is a 403) would otherwise capture it, and neither is right: it is a deterministic platform constraint, not a repository defect and not an expired credential |56| Failing step references a file the head commit's diff modified | `defect` |57| Re-run of the same `headSha` would produce green (no infra signal, no code change in the failing step's surface) | `flake` |58| HTTP 5xx, rate-limit, registry-unreachable, GitHub status incident | `infra` |59| `uses:` pin in the workflow points to a `nolte/gh-plumbing` (or other reusable) tag, and a newer tag exists with the relevant fix | `stale pin` |60| Token, deploy key, or OIDC trust expired or rotated; failure references `401`, `403`, `expired`, `unauthorized` | `secret drift` |61| None of the above | `other` (with a short note explaining why) |6263Confirm the chosen class with the user before proceeding—at minimum for `defect`, `flake`, and `secret drift`, where misclassification has the highest cost. Apply the classification by writing it to a scratch note that becomes the seed of the fix PR's Risk / rollout notes.6465**Hard:** never call `gh run rerun <id>` more than once before a recorded classification exists; repeated blind re-runs are drift per `spec/project/workflow-health/` §Triage before remediation.6667### 3. Dispatch the most specialised available agent6869The set of available agents changes over time; never freeze a snapshot of "which agents exist" inside this skill body. Resolve the dispatch target dynamically each invocation:70711. **Resolve the candidate set.** `Glob` `agents/*.md` (plus `.claude/agents/*.md` (project-distributed; additionally `~/.claude/agents/*.md` for user-level agents) for the project-distributed half), then `Read` the `description:` line of every candidate. Build a (`name`, `description`) table; the table is the runtime inventory.722. **Match classification to candidate.** Walk the candidates and pick the one whose `description` most closely matches the (classification, failing-artefact-area) pair: a `defect` in a markdown spec/skill/agent file maps to whichever agent's description names "spec-conformant authoring" or the equivalent; a `defect` in a workflow YAML maps to whichever agent's description names "workflow YAML" or "GitHub Actions"; a documentation `defect` maps to whichever agent names an audience-targeted documentation responsibility; and so on. The match is on the description's stated responsibility, not on the agent's name.733. **Recognise the no-fix classifications.** `flake` and `secret drift` produce no agent dispatch by design — the work is documenting the flake in the project's flake registry (`FLAKES.md` or the `flake`-labelled issue set, whichever the repository uses) for `flake`, or rotating the credential outside Claude for `secret drift`. The skill produces only the fix PR that re-references the rotated credential.744. **No match is a portfolio gap.** When the candidate walk produces no plausible match and the failure class has occurred three or more times historically (use `gh run list --status failure --branch develop --limit 50` plus a quick grep to estimate), surface this as a portfolio gap per `spec/project/workflow-health/` §Specialised-agent dispatch — the user is asked whether to author a new agent (via `claude-plugin-developer`) before the fix PR opens. When a match exists, dispatch with `Agent(subagent_type="<plugin>:<agent>")` and pass the classification, the run URL, the failing-step excerpt, and the fix-PR-title hint. The classification is a hypothesis about the cause, so the dispatch brief **MUST** authorise refutation per `spec/claude/dispatch-brief/`: tell the agent it may refute the classification, and treat a returned refutation (contradicting evidence plus what it did instead) as a first-class result — re-classify rather than force the fix — not a failed dispatch. Wait for the agent's report.7576The dynamic-lookup design means a new specialised agent that lands in `agents/` becomes dispatchable immediately, without a coordinated edit to this skill — and a renamed or removed agent stops being a target the next time the skill runs, with no stale snapshot to mislead the dispatch.7778### 4. Verify the fix PR carries the audit trail7980Whether the editing was done by a specialised agent or the generalist, the resulting fix PR **MUST** carry both pieces of evidence in its **Risk / rollout notes** section per the `pull-request-workflow` spec:8182- Triage classification verbatim (one of the six labels above, with a one-line note for `other`)83- Dispatched agent name (the literal `subagent_type` argument), or the literal phrase "no matching specialised agent—generalist remediation"8485If the user wants the skill to open the fix PR itself, dispatch `pull-request-create` with these two lines pre-populated in the Risk / rollout notes; the user still confirms the title and body before push, per the `pull-request-create` spec's externally-visible-action gate.8687### 5. Verify the standard PR gate8889After the fix PR opens, the skill stops. The actual merge belongs to `pull-request-merge`, which re-validates the gate. The skill **MUST NOT**:9091- merge the fix PR itself (out of scope; `pull-request-merge` owns that)92- pass `--admin` anywhere (`enforce_admins: true` on `develop` has no exception)93- waive a still-failing required check on the fix PR (the spec forbids `continue-on-error` masking and required-check removal)9495Report back the run ID, the classification, the dispatched agent name (or "generalist"), the fix-PR URL, and a one-line "next action: invoke `pull-request-merge` after CI is green".9697## Old patterns9899Earlier revisions of this skill enumerated specific agent names inline (for example `workflow-yaml-fixer`, `claude-plugin-developer`, `audience-doc-author`) as the dispatch table. That snapshot rotted whenever a new agent landed or an existing one renamed; the runtime-Glob design in Operation 3 replaces it. The historical mapping is preserved here only so a reader who recognises the prior wording can spot the transition: `defect` in workflow YAML used to fall back to generalist (no matching agent), `defect` in spec / skill / agent files used to dispatch `claude-plugin-developer`, `defect` in documentation used to dispatch `audience-doc-author`. Use the runtime lookup in Operation 3 instead of this snapshot.100101## Examples102103- Read `examples/01-defect-classification-dispatch.md` when triaging a failure that classifies as `defect` and dispatches to a specialised agent.104- Read `examples/02-stale-pin-portfolio-gap.md` when the failure root-causes to a stale pin in the portfolio plumbing.105- Read `examples/03-flake-no-fix-record-only.md` when the failure classifies as `flake` and the skill records the classification without opening a fix PR.106107## Resumability108109Per `spec/claude/resumable-work/`, this skill is `resumable: true`. State is persisted to `.resume/workflow-health-triage/<run-id>.yml` after every successful user-approval gate and after each named phase boundary. On re-invocation, scan that directory for files with `status: in_progress` whose `inputs:` snapshot matches the current invocation; if one matches, prompt the operator with `Resume run <run_id> from phase <phase> (last checkpoint <last_checkpoint_at>)? [resume / start-new / discard]`. The state-file envelope (`schema_version`, `run_id`, `inputs`, `phase`, `decisions[]`, `status`, ...) and the fail-closed semantics on schema or YAML errors are load-bearing in the spec; don't duplicate those rules here.110111## Hard rules112113- **Never** re-run a failed required workflow run more than once before a recorded triage classification exists; the spec calls repeated blind re-runs drift.114- **Never** classify a failure as `flake` without reproducible evidence (re-run of the same `headSha` returned green and no infra signal explains the first failure).115- **Never** mask a failure: don't propose `continue-on-error: true` on a required job, don't propose removing a check from the required-checks set in `.github/settings.yml` without a tracking Issue, don't propose repointing a `nolte/gh-plumbing` pin from a tag to a branch.116- **Never** bypass branch protection on the fix PR; `enforce_admins: true` on `develop` has no exception path.117- **Never** dispatch a specialised agent with a classification the spec doesn't list. The closed set is `defect / flake / infra / stale pin / secret drift / other`.118- **Never** open a fix PR whose Risk / rollout notes don't carry the classification and the dispatched agent name (or the explicit "no matching specialised agent" note).119- **Always** prefer a plugin-distributed specialised agent over the generalist when one matches; the spec's §Specialised-agent dispatch makes this a SHOULD that this skill upgrades to a hard contract for the dispatch step.120- When `spec/project/workflow-health/` disagrees with this skill, the spec wins. Propose updating this skill rather than silently diverging.121122## Gotchas123124Per `spec/claude/skill-management/` §Gotchas—concrete corrections to non-obvious environment facts the executing agent would otherwise get wrong.125126- **`GITHUB_TOKEN`-cascade failures aren't `defect`.** A `release-drafter.yml` that doesn't fire after an `automerge.yaml` squash-merge, or a `release-cd-refresh-master.yml` that doesn't fire after `release-publish.yml`, is the documented `infra` class per `spec/project/workflow-health/` §Known platform constraints—and the remediation is upstream in `nolte/gh-plumbing`, not in the consumer repo. Don't open a fix PR against the consumer's workflow YAML; document the `infra` classification and reference the `nolte/gh-plumbing` tracking Issue.127- **A workflow-file push rejection reads like `secret drift` and isn't.** `refusing to allow a GitHub App to create or update workflow ... without 'workflows' permission` is a 403-flavoured rejection, so the `secret drift` row matches it on the surface. It's the second constraint in `spec/project/workflow-health/` §Known platform constraints: `GITHUB_TOKEN` is a GitHub App identity and can never write under `.github/workflows/`, and the `permissions:` block has no `workflows` scope to grant. Rotating a credential fixes nothing. Classify `infra` and route to the App-token remediation, whose installation needs `workflows: write` on top of `contents: write`. It surfaces intermittently, only when the pushed diff happens to touch a workflow file, so a green history doesn't rule it out.128- **`pascalgn/automerge-action` exits 0 on `mergeResult: 'merge_failed'`.** A `automerge.yaml` run with conclusion `success` whose log carries `mergeResult: 'merge_failed'` or `Failed to merge PR:` is a `stale pin` failure (the reusable's `MERGE_METHOD` default doesn't match the repo's allowed strategy in some pre-fix versions). Triage the `automerge.yaml` `uses:` tag, not the workflow YAML itself.129- **Renovate-generated bump PRs for `nolte/gh-plumbing` aren't automerged in this portfolio.** A `stale pin` remediation that proposes to enable Renovate automerge for `nolte/gh-plumbing` violates `workflow-health` §Upstream drift and the AC against it. The remediation is a human-acknowledged Renovate PR, not an automerge rule.130- **`flake` without reproducible evidence is `defect`.** A "let's just re-run and hope" reflex is exactly what the spec forbids. If the same `headSha` doesn't re-run cleanly green and no infra signal explains the first failure, the class is `defect` and the work is a fix, not a tracking entry.131132## Multi-model testing133134Examples and operations in this skill are verified on Claude Sonnet as the default model; spot-checked on Haiku for cost-sensitive runs; Opus is appropriate for high-stakes audits that require deeper reasoning. The skill body has no model-specific assumptions beyond standard tool-call semantics.