src/skills/infra-ci-cd-turborepo-ci/SKILL.md
Turborepo CI pipelines with remote caching and affected detection
npx skillsauth add agents-inc/skills infra-ci-cd-turborepo-ciInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use
--affectedfor PR builds (auto-detects CI environment, falls back to full suite on shallow clones). Enable Remote Cache withTURBO_TOKEN+TURBO_TEAMenv vars. Declareoutputsfor every cacheable task or cached results will be incomplete. UseenvandglobalEnvin turbo.json to include environment variables in cache hashes -- missing entries cause cross-environment cache collisions. Pinturboversion in CI. Useturbo query affectedto conditionally skip entire CI steps.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST declare outputs for every cacheable task in turbo.json -- missing outputs means cached results restore without build artifacts)
(You MUST list environment variables that affect task output in env (task-level) or globalEnv (all tasks) -- omitting them causes cross-environment cache hits with wrong values)
(You MUST use --affected for PR builds -- running the full task graph on PRs wastes CI time on unchanged packages)
(You MUST pin the turbo CLI version in CI -- latest can introduce breaking changes mid-pipeline)
</critical_requirements>
Detailed Resources:
Auto-detection: Turborepo CI, turbo.json, turbo run, turbo prune, --affected, --filter, TURBO_TOKEN, TURBO_TEAM, Remote Cache, turbo query affected, outputs, dependsOn, globalEnv, envMode, concurrency, turbo login, turbo link, cache artifacts, monorepo CI
When to use:
--affected to run only changed-package tasks on PRsturbo prune --docker--summarize or --dryturbo query affectedWhen NOT to use:
Key patterns covered:
outputs, env, dependsOn, cache, inputs)TURBO_TOKEN, TURBO_TEAM, signature verification)--affected, --filter=...[origin/main], turbo query affected)turbo prune --docker and multi-stage builds--summarize, --dry, --force)strict vs loose) and passThroughEnvTurborepo's CI value comes from two things: caching (never redo work whose inputs haven't changed) and affected detection (never start work that can't have changed). The combination turns a 15-minute full monorepo build into a sub-minute cache restore for unchanged packages.
Core CI principles:
outputs and all env variables that affect task results.--affected. Main branch runs the full task graph to catch integration issues.latest in CI means non-reproducible builds. Pin to the major version at minimum.When to use Turborepo in CI:
When NOT to use:
Every task that produces files must declare outputs. Every task affected by environment variables must declare env. Missing either causes cache correctness issues.
{
"$schema": "https://turborepo.dev/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", "build/**"],
"env": ["NODE_ENV", "API_URL"],
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"env": ["DATABASE_URL"],
},
"lint": {
"dependsOn": [],
"cache": true,
},
"type-check": {
"dependsOn": ["^build"],
"cache": true,
},
},
}
Key decisions:
dependsOn: ["^build"] means "run build in all dependencies first" -- the ^ prefix means upstream packagesoutputs defines what gets cached and restored -- omit it and cached runs produce empty resultsenv includes variables in the cache hash -- change the value, bust the cachecache: false disables caching for tasks like dev or deployment scriptsSee examples/core.md for complete task config with inputs, passThroughEnv, outputLogs, and package-level overrides.
Remote Cache shares build artifacts across CI runners and developer machines. Two environment variables enable it.
# Required for Remote Cache in CI
TURBO_TOKEN=<bearer-token> # Auth token (Vercel or self-hosted)
TURBO_TEAM=<team-slug> # Team/account identifier
Vercel Remote Cache: Free, automatic on Vercel deployments. For other CI providers, set TURBO_TOKEN and TURBO_TEAM as CI secrets.
Self-hosted: Use TURBO_API to point to a custom Remote Cache server implementing the Turborepo Remote Cache API. Community options: ducktors/turborepo-remote-cache, brunojppb/turbo-cache-server.
Signature verification (recommended for shared caches):
{
"remoteCache": {
"signature": true,
},
}
Set TURBO_REMOTE_CACHE_SIGNATURE_KEY with an HMAC-SHA256 secret. Failed verification is treated as a cache miss.
See examples/remote-cache.md for complete setup including self-hosted config and cache permission tuning.
--affected runs tasks only in packages with code changes. Auto-detects CI environment variables (GITHUB_BASE_REF, etc.) to determine the comparison base.
# PR builds: only changed packages
turbo run build test lint --affected
# Manual comparison base
turbo run test --filter=...[origin/main]
Gotcha: Shallow clones break affected detection because git diff needs history. --affected gracefully falls back to running all tasks, but --filter=...[origin/main] may fail silently. Ensure sufficient clone depth in CI.
Advanced: Conditional CI steps with turbo query affected:
# Check if a specific package is affected before running expensive steps
AFFECTED=$(turbo query affected --packages web \
| jq '.data.affectedPackages.length')
if [ "$AFFECTED" -gt 0 ]; then
# Run expensive deployment steps
fi
See examples/affected-detection.md for PR vs main branch patterns and turbo query affected examples.
turbo prune generates a sparse monorepo with only the packages needed to build a target. The --docker flag splits output for Docker layer caching.
# Stage 1: Install dependencies (cached unless lockfile changes)
FROM node:20-alpine AS deps
WORKDIR /app
COPY out/json/ .
RUN npm install --frozen-lockfile
# Stage 2: Build (cached unless source changes)
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app .
COPY out/full/ .
RUN npx turbo run build --filter=web
Key benefit: Changes to source code in one package don't invalidate the dependency install layer for other packages. Without pruning, any lockfile change (even in unrelated packages) busts the Docker cache for all images.
See examples/docker.md for complete multi-stage Dockerfile with turbo prune --docker.
When tasks produce unexpected results after cache hits, use these tools to diagnose.
# See what would run without executing (dry run)
turbo run build --dry
# Generate detailed run summary with hashes and timing
turbo run build --summarize
# Output in .turbo/runs/<id>.json
# Force re-execution, ignoring cache
turbo run build --force
# Control cache sources (disable remote, keep local)
turbo run build --cache=local:rw,remote:off
Common cache miss causes:
env or globalEnvinputs key to customize)turbo version between CI and local (different hashing algorithm)outputs declaration (task runs but restores nothing from cache)See examples/core.md for --summarize output analysis and cache troubleshooting.
Turborepo's strict env mode (default in v2) only makes variables listed in env, globalEnv, or passThroughEnv available to tasks. This prevents accidental cache collisions but requires explicit configuration.
{
"globalEnv": ["CI", "NODE_ENV"],
"tasks": {
"build": {
"env": ["API_URL", "SENTRY_DSN"],
"passThroughEnv": ["npm_config_registry"],
},
},
}
Decision: env vs globalEnv vs passThroughEnv:
| Key | Affects hash? | Available to task? | Scope |
| ---------------------- | --------------- | ------------------ | ----------- |
| env | Yes | Yes | Single task |
| globalEnv | Yes (all tasks) | Yes | All tasks |
| passThroughEnv | No | Yes | Single task |
| globalPassThroughEnv | No | Yes | All tasks |
When to use passThroughEnv: Variables needed at runtime but that don't affect build output (e.g., npm_config_registry, HTTP_PROXY). Changing them should not bust the cache.
See examples/core.md for strict vs loose mode examples and environment variable debugging.
</patterns>Goal: PR builds < 3 minutes with affected detection + Remote Cache
Cache hit rate optimization:
outputs and env variables to prevent false misses--summarize to compare hashes between runs and identify unexpected invalidationsglobalEnv minimal -- variables there bust cache for ALL tasksenv for variables that only affect specific tasksConcurrency tuning:
# Default concurrency is 10 parallel tasks
turbo run build test lint --concurrency=20
# Use percentage of available CPUs
turbo run build --concurrency=50%
# Serial execution (debugging)
turbo run build --concurrency=1
Output log filtering for CI readability:
{
"tasks": {
"build": {
"outputLogs": "new-only",
},
"lint": {
"outputLogs": "errors-only",
},
},
}
Options: full (default), hash-only, new-only, errors-only, none.
Monitoring targets:
<decision_framework>
Running a PR build?
|-- YES --> Use --affected (auto-detects CI env, graceful shallow clone fallback)
+-- NO --> Need specific package selection?
|-- YES --> Use --filter (explicit package/directory/git targeting)
+-- NO --> Run full task graph (main branch, release builds)
Team > 1 person OR using CI?
|-- YES --> Enable Remote Cache (shared artifacts save significant time)
| |-- Using Vercel for hosting?
| | |-- YES --> Free, auto-configured on Vercel deployments
| | +-- NO --> Set TURBO_TOKEN + TURBO_TEAM as CI secrets
| +-- Need artifact signing?
| |-- YES --> Enable signature verification in turbo.json
| +-- NO --> Default (unsigned) is fine for trusted environments
+-- NO --> Local cache sufficient (solo developer, single machine)
Does the variable affect task output (build artifacts, test results)?
|-- YES --> Does it affect ALL tasks or just one?
| |-- ALL tasks --> globalEnv (changes bust cache for everything)
| +-- One task --> env on that specific task
+-- NO --> Does the task need it at runtime?
|-- YES --> passThroughEnv (available but not in hash)
+-- NO --> Don't list it (strict mode blocks it)
Building Docker images from monorepo?
|-- YES --> Does your Dockerfile install from root lockfile?
| |-- YES --> Use turbo prune --docker (pruned lockfile = better layer caching)
| +-- NO --> Standard turbo prune (no --docker flag needed)
+-- NO --> Not applicable
</decision_framework>
<red_flags>
High Priority:
outputs on cacheable tasks -- cached runs restore nothing, tasks appear to succeed but produce no artifactsenv for environment-dependent tasks -- build with API_URL=staging hits cache from API_URL=production, serving wrong configlatest turbo version in CI -- non-reproducible builds, potential breaking changes mid-pipeline--filter=...[origin/main] fails when git history is insufficient; --affected handles this gracefully by falling back to full suiteMedium Priority:
globalEnv -- every change busts cache for ALL tasks; use task-level env for task-specific variables--affected on PR builds -- full task graph on PRs wastes CI time rebuilding unchanged packagesturbo run explicitly in CI -- bare turbo build may conflict with future CLI subcommandsCommon Mistakes:
dependsOn: ["^build"] for tasks that consume upstream package outputs (test/lint fail because dependency not built)loose env mode and wondering why cache hits serve wrong environment configglobalDependencies for files like .env or tsconfig.base.json that affect all packagescache: false on tasks that should be cached (e.g., test, lint) because of past debugging and forgetting to re-enableGotchas & Edge Cases:
--affected auto-detects GitHub Actions via GITHUB_BASE_REF -- other CI providers may need manual --filter insteadturbo query affected returns JSON -- parse with jq for conditional CI stepsoutputs globs are relative to the package directory, not the repo root$TURBO_DEFAULT$ in inputs restores the default input behavior when you only want to add inputs, not replace themsignature: true requires TURBO_REMOTE_CACHE_SIGNATURE_KEY -- missing key means cache reads fail silently (treated as misses)--summarize output goes to .turbo/runs/ -- useful for diffing hashes between two runs to find what changed--parallel flag is deprecated -- use persistent: true in turbo.json for long-running tasks insteadturbo-ignore is deprecated -- use turbo query affected instead for conditional CI stepsremoteCache.timeout default is 30s, uploadTimeout is 60s -- increase for large monorepo artifacts</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST declare outputs for every cacheable task in turbo.json -- missing outputs means cached results restore without build artifacts)
(You MUST list environment variables that affect task output in env (task-level) or globalEnv (all tasks) -- omitting them causes cross-environment cache hits with wrong values)
(You MUST use --affected for PR builds -- running the full task graph on PRs wastes CI time on unchanged packages)
(You MUST pin the turbo CLI version in CI -- latest can introduce breaking changes mid-pipeline)
Failure to follow these rules will cause incorrect cache hits (wrong build artifacts served), slow CI (full rebuilds on every PR), and non-reproducible pipelines.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events