Add paths-ignore, concurrency groups, and timeouts to existing GitHub Actions workflows. Use for "reduce CI costs"; for new CI, use set-up-ci.
Scanned 10/2/2026
npx -y skills add cboone/agent-harness-plugins --skill optimize-runner-usage --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Optimize Runner Usage?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/cboone-optimize-runner-usage)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: optimize-runner-usage
description: >-
Add paths-ignore, concurrency groups, and timeouts to existing GitHub Actions
workflows. Use for "reduce CI costs"; for new CI, use set-up-ci.
---
# Optimize Runner Usage
Scan existing GitHub Actions workflows and add missing runner-usage optimizations: `paths-ignore` filters, concurrency groups, and `timeout-minutes` on jobs.
Private GitHub repos pay for Actions minutes, and macOS runners cost 10x Linux runners. These optimizations reduce wasted minutes by skipping unnecessary runs, cancelling superseded builds, and preventing runaway jobs.
## Workflow
### 1. Scan for Workflow Files
Use Glob to find all `.yml` and `.yaml` files in `.github/workflows/`.
If the `.github/workflows/` directory does not exist, abort with:
> No `.github/workflows/` directory found. This skill optimizes existing GitHub Actions workflows. Use the `set-up-ci` skill to create a CI workflow from scratch.
If no workflow files are found, abort with:
> No workflow files found in `.github/workflows/`. Use the `set-up-ci` skill to create a CI workflow.
### 2. Classify Each Workflow
Read each workflow file and classify it based on its `on:` triggers.
GitHub Actions allows `on:` in multiple forms:
- Mapping form: `on: { push: ..., pull_request: ... }` (or multi-line equivalents)
- Single-event shorthand: `on: push`
- Multi-event shorthand: `on: [push, pull_request]`
Before classifying, normalize shorthand forms to mapping shape:
- `on: push` becomes `on: { push: {} }`
- `on: [push, pull_request]` becomes `on: { push: {}, pull_request: {} }`
If applying edits would require rewriting `on:` from shorthand into a mapping (for example, to add `paths-ignore` under `push:`), perform the rewrite explicitly, updating `on:` to a mapping while preserving existing semantics.
Then classify workflows using the normalized `on:` structure:
| Trigger pattern | Classification |
| ------------------------------------------------------------- | --------------- |
| `pull_request:` (any configuration) | CI |
| `push:` with `branches:` (optionally plus `pull_request:`) | CI |
| `push:` with `tags:` only (no `branches:` or `pull_request:`) | Release |
| `push:` with both `branches:` and `tags:` | Mixed |
| `schedule:` / `workflow_dispatch:` only | Scheduled |
| Bare `push:` with no filters | Broad push |
| Workflow name or filename contains secret scanning keyword | Secret scanning |
| `workflow_call:` trigger | Reusable |
Secret scanning keywords: "gitleaks", "trufflehog", "secret", "scan" (case-insensitive).
Secret scanning is a subset of Broad push: if a workflow has a bare `push:` and matches a secret scanning keyword, classify it as Secret scanning rather than Broad push.
For the secret scanning keyword check, inspect in this order:
1. The workflow-level `name:` field (if present)
1. The workflow filename (without directory path) as a fallback
1. Optionally, job-level `name:` values as additional signals
Mixed workflows (both `branches:` and `tags:` on push) are not eligible for `paths-ignore` because it cannot be scoped to branch pushes only. They use `cancel-in-progress: false` for concurrency.
### 3. Analyze Each Workflow for Missing Optimizations
For each workflow file, check which of the three optimizations are already present and which are needed.
#### paths-ignore
**Eligible**: CI workflows only.
**Not eligible**: Release, Mixed, Scheduled, Secret scanning, Broad push, or Reusable workflows. Also not eligible if the trigger already has a `paths:` (positive filter), since `paths` and `paths-ignore` are mutually exclusive in GitHub Actions. Mixed workflows are excluded because `paths-ignore` under `push:` applies to both branch and tag pushes and cannot be scoped to branches only.
Standard `paths-ignore` list:
```yaml
paths-ignore:
- "*.md"
- "docs/**"
- "LICENSE"
- ".editorconfig"
- ".claude/**"
- "**/CLAUDE.md"
- "**/AGENTS.md"
```
Add `paths-ignore` under each eligible trigger (`push:` and `pull_request:`).
#### Concurrency
**Eligible**: All workflow types.
Two concurrency group patterns are used, depending on classification:
**Ref-scoped pattern** (CI, Scheduled, Broad push, Mixed, Secret scanning, Reusable):
```yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # or false, per table below
```
**Workflow-scoped pattern** (Release only):
```yaml
concurrency:
group: ${{ github.repository }}-${{ github.workflow }}
cancel-in-progress: false
```
Release workflows use the workflow-scoped pattern because each tag push produces a unique `github.ref` (e.g., `refs/tags/v1.0.0`), so including it in the group never deduplicates concurrent runs. The workflow-scoped pattern serializes all runs of the same release workflow regardless of which tag triggered them.
`cancel-in-progress` values by classification:
| Classification | `cancel-in-progress` | Concurrency pattern |
| ------------------------- | -------------------- | ------------------- |
| CI, Scheduled, Broad push | `true` | Ref-scoped |
| Mixed, Secret scanning | `false` | Ref-scoped |
| Release | `false` | Workflow-scoped |
| Reusable | `true` | Ref-scoped |
If an existing concurrency group is present but uses a different `group:` expression than the expected pattern for that classification, flag it for user review. Do not overwrite non-standard concurrency groups automatically.
If an existing concurrency group matches the expected pattern for that classification, check only whether `cancel-in-progress` needs updating.
#### timeout-minutes
**Eligible**: All jobs that lack a `timeout-minutes` key.
Assign timeouts by heuristic:
| Job indicator | Timeout |
| ------------------------------------------------------------- | ------- |
| Release, publish, or deploy jobs (by job name or steps) | 30 min |
| Rust build jobs (steps contain `cargo build` or `cargo test`) | 20 min |
| Vuln check, shellcheck, or shell lint jobs | 10 min |
| All other CI jobs | 15 min |
### 4. Present Summary and Confirm
Display a summary table of all workflows with their classifications and proposed changes:
```text
File Classification paths-ignore concurrency timeout
ci.yml CI + add + add + add (15m)
release.yml Release skip + add + add (30m)
gitleaks.yml Secret scanning skip + add + add (15m)
```
Use `+` for additions, `skip` for not applicable, and a checkmark or note for already present.
**Markdown as source code**: Before applying `paths-ignore` to any workflow, ask once whether the project treats Markdown files as source code (e.g., a documentation-focused project where Markdown linting is a CI step). If yes, remove `"*.md"` from the `paths-ignore` list for all workflows.
**Confirmation**: Ask the user to confirm changes for each workflow individually, with options:
1. **Apply all**: Apply all proposed changes to all workflows
1. **Review each**: Step through each workflow one at a time
1. **Skip**: Skip a specific workflow
1. **Abort**: Stop without making any changes
### 5. Apply Changes
Use the Edit tool to apply changes. Order within each file:
1. **paths-ignore** first (under each eligible trigger)
1. **Concurrency group** second (top-level, after the `on:` block and before the next top-level key)
1. **timeout-minutes** third (on each job, after `runs-on:`)
Preserve existing YAML structure, indentation, and comments. Do not reformat or reorder existing content.
### 6. Summary
Print a final summary:
- List every modified file and what was added to each
- Note any skipped workflows and why (already optimized, user skipped, flagged for manual review)
- Suggest running CI to verify the changes do not break anything:
> Run your CI pipeline to verify the optimized workflows behave correctly. If any workflow skips when it should run, check the `paths-ignore` patterns.
## Edge Cases
- **`paths:` already present**: Do not add `paths-ignore` (mutually exclusive in GitHub Actions). Note in the summary.
- **Existing non-standard concurrency group**: Do not overwrite. Flag for the user to review manually. The expected pattern depends on classification: workflow-scoped for Release, ref-scoped for all others.
- **Tag-triggered workflows (Release)**: Use the workflow-scoped concurrency pattern (`${{ github.repository }}-${{ github.workflow }}`) instead of the ref-scoped pattern, because each tag produces a unique `github.ref` that never deduplicates. Always use `cancel-in-progress: false`.
- **Mixed triggers (branches + tags on push)**: Do not add `paths-ignore` (cannot be scoped to branches only without splitting the workflow). Add concurrency with `cancel-in-progress: false` and `timeout-minutes` as normal.
- **Already fully optimized**: Skip with a note that no changes are needed.
- **Reusable workflows (`workflow_call:`)**: Skip `paths-ignore`. Add concurrency and timeouts normally.
- **Markdown as source code**: User-confirmed. Remove `"*.md"` from the `paths-ignore` list.
- **Unparseable YAML**: Skip the file with a warning and continue with remaining files.
## Error Handling
- **No `.github/workflows/` directory**: Abort with a clear message suggesting the set-up-ci skill.
- **No workflow files found**: Abort with a clear message suggesting the set-up-ci skill.
- **YAML parse errors**: Skip the individual file with a warning and continue processing other files.
- **Empty workflow file**: Skip with a note.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!