Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/illideristudios/ci-optimization-skill itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 26, 2026GitLab CI/CD Optimization Skill (ci-optimization-skill)
An agent skill, two small Python tools, and four pipeline templates for designing, auditing, and optimizing GitLab CI/CD pipelines (.gitlab-ci.yml).
Derived from the Gauntlet GitLab Primer and modern CI/CD engineering best practices.
⚡ The Problem: Why CI Pipelines Break & Waste Budget
- Exit Code 137 (OOM Kills): Runner containers (typically 1GB–4GB RAM, 2 vCPUs) report host hardware (e.g. 16–32 CPUs). Tools that size their worker pool from the CPU count (Jest, Vitest,
pytest -n auto) spawn dozens of worker processes, causing catastrophic CPU thrashing and immediate kernel OOM termination (128 + 9 = SIGKILL). - The 3 Setup Taxes: Every job pays taxes pulling images, installing OS packages (
apt-get,curl,jq), and reinstalling language dependencies. On small runners, overhead dominates real testing/building time. - Duplicate Pipelines: Pushing to a branch with an open Merge Request often triggers two identical pipelines (branch pipeline + MR pipeline), doubling CI runner billing.
- Queue Waves: Sharding tests into 8 parallel jobs on a cluster with only 2 concurrent runner slots causes jobs to queue in waves, multiplying wall-clock wait times.
🛡️ The Core Optimization Doctrine
1. Zero Exit 137 OOM Kills
- Cap the Workers: Pin test processes to the runner's vCPUs (2 on Gauntlet runners):
- Node.js:
npx jest --maxWorkers=2 --workerIdleMemoryLimit=512MB - Python:
uv run pytest -n 2(needspytest-xdist; without-npytest is already serial) - Go:
go test -p 2withGOMAXPROCS="2"
- Node.js:
- Cap Runtime Heap: The ceilings add up to 75% of container RAM, and each applies per process, so divide by the processes the job runs. On a 2048 MB runner:
- Node.js:
NODE_OPTIONS="--max-old-space-size=1536"for one process,512under Jest with 2 workers - Go:
GOMEMLIMIT="768MiB"withgo test -p 2 - JVM:
JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0" - Python: CPython has no heap cap;
MALLOC_ARENA_MAX="2"limits fragmentation and the worker cap bounds memory
- Node.js:
2. Pay the Setup Tax Once
- Prebuilt CI Image (Layer 1): Keep tools in
Dockerfile.cibuilt in.prestage only when the Dockerfile changes on the default branch, pushing to$CI_REGISTRY_IMAGE/ci:latest. - Install Once, Sync Everywhere (Layer 2): A single
installjob with frozen lockfiles (npm ci,uv sync --frozen,go mod download) fills a cache keyed on the lockfile (policy: pull-push), and the jobs that need it pull that cache (policy: pull). Vendor folders (node_modules/,.venv/,.nuget/,obj/) are never artifacts. - Artifacts only where they must exist: Most jobs upload nothing. An artifact is allowed only when a later job or a person needs that exact output and cannot rebuild it from the cache (the bundle a deploy ships, a readable report), and it sets
expire_in: 1 hour. - DAG Execution with
needs:(Layer 3): Downstream jobs (lint,test,build) declareneeds: [install]to start immediately with dependencies already mounted on disk.
A(j) := job j uploads artifacts to GitLab
M(j) := a later job or a person needs j's exact output and cannot rebuild it from the cache
V(j) := j's output is a dependency/vendor folder (node_modules/, .venv/, .nuget/, obj/)
E(j) := j's artifacts set expire_in: 1 hour
1. A(j) → M(j) [Zach, 2026-10-19: "artifacts THAT MUST BE THERE expire after 1 hour"]
2. V(j) → ¬M(j) [the install job's lockfile-keyed cache carries dependencies to every consumer]
3. A(j) → E(j) [Zach, 2026-10-19]
4. V(j) → ¬A(j) [2, then the contrapositive of 1: ¬M(j) → ¬A(j)]
∴ install jobs upload nothing, and any artifact that does exist expires in 1 hour
3. Pipeline Hygiene & Concurrency Tuning
- One Pipeline per Push: Enforce top-level
workflow: rules:to prioritize MR pipelines and eliminate duplicate branch runs. - Auto-Cancel Stale Runs:
default: interruptible: truecancels outdated pipelines when new commits are pushed. Deploy, publish and image-build jobs setinterruptible: false. - Tight Timeouts:
default: timeout: 15mstops a hung job from draining the runner quota. - Shallow Clone:
variables: GIT_DEPTH: "20"skips downloading full git commit histories. - Shards <= Runner Slots: Ensure
parallel: Ntest shards never exceed available concurrent runner slots. - Offload Heavy Scans: Move end-to-end browser suites (Playwright/Cypress) and full SAST/DAST security scans to schedules (
$CI_PIPELINE_SOURCE == "schedule") or manual triggers.
📦 Install
Claude Code installs both skills as a plugin, from GitHub or from a local checkout:
/plugin install gitlab-ci-optimizer --marketplace locriani/ci-optimization-skill
# or: claude plugin marketplace add ~/path/to/ci-optimization-skill && claude plugin install gitlab-ci-optimizer@gitlab-ci-optimizer
Other agents: the scripts need Python 3 and nothing else. Every agent file refers to the skill at .agents/skills/gitlab-ci-optimizer/, so copy skills/ there in the project whose pipeline you are working on:
git clone https://github.com/locriani/ci-optimization-skill
cd your-project
mkdir -p .agents && cp -R ../ci-optimization-skill/skills .agents/skills
Then copy the adapter for each agent you use:
| Agent | Copy into your project | What it does |
|---|---|---|
| Google Antigravity | nothing more | Loads both skills from .agents/skills/. |
| Claude Code | .claude/commands/simulate-budget.md | Adds the /simulate-budget command. |
| Cursor | .cursorrules, .cursor/rules/*.mdc | Attaches the optimization rules when a .gitlab-ci*.yml or Dockerfile.ci is open; /simulate-budget rule. |
| OpenAI Codex / Copilot | CODEX.md, AGENTS.md, .codex/instructions.md | Multi-agent instructions and CLI memory budgeting guides. |
In this repository .agents/skills is a symlink to skills/, so the same paths work here.
🧰 Included Tools & Scripts
1. Runner Memory & Concurrency Simulator (/simulate-budget)
Estimates peak RAM per test job, flags OOM risk, and estimates wall-clock time on a given number of runner slots:
python3 skills/gitlab-ci-optimizer/scripts/simulate_budget.py \
--stack node \
--ram 2048 \
--vcpus 2 \
--slots 2 \
--workers 2 \
--shards 2
--no-prebuilt and --no-install-once model a pipeline without those two fixes. The exit code is 1 when the job would be OOM killed. The simulator does not read your pipeline: suite duration and per-worker memory are illustrative constants, so it compares configurations and does not predict a real job.
2. 11-Point Lean Scorecard Pipeline Auditor
Audits any .gitlab-ci.yml against the scorecard below and prints a Lean Score from 0 to 100, the share of applicable checks that pass:
python3 skills/gitlab-ci-optimizer/scripts/audit_pipeline.py path/to/.gitlab-ci.yml
Pass --ram and --max-workers when the runner is not 2048 MB / 2 vCPU. The exit code is 0 when every applicable check passes, 1 when one fails, and 2 when the file cannot be audited. A check that does not apply to the pipeline (no artifacts, no test command, a parent pipeline that only triggers children) is reported as N/A.
The checks:
- One pipeline per push (
workflow: rules) - Stale pipelines cancel (
default: interruptible: true) - Default job timeout <= 15m
- Every
artifacts:block expires within 1 hour - Shallow git clone (
GIT_DEPTH1-20) - Test workers capped to runner vCPUs (
--maxWorkers,-n,-p,--workers) - Runtime heap ceiling, summed over a job's processes, <= 75% of runner RAM (
NODE_OPTIONS,GOMEMLIMIT) - Dependencies installed once and shared by cache: one
installjob fills a lockfile-keyed cache (pull-push), the jobs thatneeds:it pull it (pull), and no install job uploads artifacts - No tool installs at job time (prebuilt CI image)
- Every test or trigger job on the push path skips untouched code (
rules: changes:) - E2E and security scans off the push path (
scheduleormanual)
The auditor matches text in one block-style YAML file with comments removed. It does not follow include:, extends: or anchors, so read the YAML before acting on a result.
Tests
python3 -m unittest discover -s tests
📁 Repository Layout
.
├── SKILL.md # Master skill entrypoint
├── README.md # Project documentation
├── AGENTS.md # Universal multi-agent specification
├── CODEX.md # OpenAI Codex configuration & field manual
├── .cursorrules # Project-level Cursor AI instructions
├── .agents/skills -> ../skills # The path every agent file uses
├── .claude/commands/
│ └── simulate-budget.md # Claude Code /simulate-budget slash command
├── .cursor/rules/
│ ├── gitlab-ci.mdc # Auto-trigger rule for .gitlab-ci*.yml files
│ └── simulate-budget.mdc # Cursor slash command / simulation rule
├── .codex/
│ └── instructions.md # Codex workspace prompt
├── scripts/ references/ examples/ # Root copy of the skill's directories (a test keeps them identical)
├── skills/
│ ├── gitlab-ci-optimizer/ # Packaged skill bundle
│ │ ├── SKILL.md # Skill entrypoint: the rules and the workflow
│ │ ├── scripts/
│ │ │ ├── audit_pipeline.py # 11-point lean checklist auditor CLI
│ │ │ └── simulate_budget.py # Runner memory & concurrency simulator CLI
│ │ ├── references/
│ │ │ ├── memory-and-oom.md # Exit 137, cgroups, host-core trap & heap caps
│ │ │ ├── setup-tax-and-caching.md # The 3 taxes, Dockerfile.ci & install-once DAG
│ │ │ └── dag-and-sharding.md # needs:, parallel: N sharding, workflow:rules
│ │ └── examples/
│ │ ├── node.gitlab-ci.yml # 100/100 lean template for Node.js / TypeScript
│ │ ├── python.gitlab-ci.yml # 100/100 lean template for Python (uv + Pytest)
│ │ ├── go.gitlab-ci.yml # 100/100 lean template for Go (go mod + go test)
│ │ └── monorepo.gitlab-ci.yml # Parent pipeline that triggers per-package children
│ └── simulate-budget/ # /simulate-budget skill (uses the simulator above)
└── tests/
└── test_scripts.py # Tests for both scripts; the templates must pass the auditor
📜 License
MIT License. See LICENSE for details.