turborepo-caching
Flagged{"isSafe":false,"isSuspicious":true,"riskLevel":"high","findings":[{"category":"system_harm","severity":"high","description":"Self-hosted remote cache (Template 4) uses an unvalidated request parameter (hash) to build file system paths. This can enable path traversal to read/write arbitrary files outside the cache directory, potentially exposing sensitive data or overwriting files.","evidence":"const filePath = join(CACHE_DIR, team, hash);"},{"category":"system_harm","severity":"high","description":"PUT endpoint streams the entire request body to disk without size limits or validation. An attacker could fill disk space or overwrite unintended files via crafted uploads.","evidence":"const stream = createWriteStream(filePath); req.pipe(stream);"},{"category":"data_exfiltration","severity":"high","description":"GET/PUT/HEAD endpoints expose artifact data without authentication. Unauthorized access could leak artifacts or allow tampering with cached content.","evidence":"app.get(\"/v8/artifacts/:hash\", ...); app.put(\"/v8/artifacts/:hash\", ...); app.head(\"/v8/artifacts/:hash\", ...);"},{"category":"data_exfiltration","severity":"high","description":"Artifact cache is accessible by potentially anyone who can reach the server (no auth checks shown). Could enable data leakage or manipulation of cached artifacts.","evidence":"app.get(\"/v8/artifacts/:hash\", async (req, res) => { ... }); app.put(\"/v8/artifacts/:hash\", async (req, res) => { ... }); app.head(\"/v8/artifacts/:hash\", async (req, res) => { ... });"},{"category":"other","severity":"low","description":"Code quality/security note: The code imports createReadStream and createWriteStream from 'fs' but references fs.access in the HEAD handler without importing/using 'fs' as a module. This would cause runtime errors and indicates refactoring gaps.","evidence":"import { createReadStream, createWriteStream } from \"fs\"; ... await fs.access(filePath);"}],"summary":"The skill content is generally safe as a caching/configuration guide, but Template 4 introduces a security risk: unvalidated path components allow path traversal to read/write arbitrary files and the endpoints expose artifacts without authentication. Additionally, there is a code quality issue (fs not imported) that could cause runtime errors. Recommendations: sanitize and validate the hash/path, resolve to an allowed base path and verify it stays under CACHE_DIR, require authentication/authorization for artifact endpoints, implement upload size limits and proper error handling, and fix the missing/fs import to avoid runtime failures."}
npx machina-cli add skill wshobson/agents/turborepo-caching --openclawTurborepo Caching
Production patterns for Turborepo build optimization.
When to Use This Skill
- Setting up new Turborepo projects
- Configuring build pipelines
- Implementing remote caching
- Optimizing CI/CD performance
- Migrating from other monorepo tools
- Debugging cache misses
Core Concepts
1. Turborepo Architecture
Workspace Root/
├── apps/
│ ├── web/
│ │ └── package.json
│ └── docs/
│ └── package.json
├── packages/
│ ├── ui/
│ │ └── package.json
│ └── config/
│ └── package.json
├── turbo.json
└── package.json
2. Pipeline Concepts
| Concept | Description |
|---|---|
| dependsOn | Tasks that must complete first |
| cache | Whether to cache outputs |
| outputs | Files to cache |
| inputs | Files that affect cache key |
| persistent | Long-running tasks (dev servers) |
Templates
Template 1: turbo.json Configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env", ".env.local"],
"globalEnv": ["NODE_ENV", "VERCEL_URL"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"env": ["API_URL", "NEXT_PUBLIC_*"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"outputs": [],
"cache": true
},
"typecheck": {
"dependsOn": ["^build"],
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"clean": {
"cache": false
}
}
}
Template 2: Package-Specific Pipeline
// apps/web/turbo.json
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"pipeline": {
"build": {
"outputs": [".next/**", "!.next/cache/**"],
"env": ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_ANALYTICS_ID"]
},
"test": {
"outputs": ["coverage/**"],
"inputs": ["src/**", "tests/**", "jest.config.js"]
}
}
}
Template 3: Remote Caching with Vercel
# Login to Vercel
npx turbo login
# Link to Vercel project
npx turbo link
# Run with remote cache
turbo build --remote-only
# CI environment variables
TURBO_TOKEN=your-token
TURBO_TEAM=your-team
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build
run: npx turbo build --filter='...[origin/main]'
- name: Test
run: npx turbo test --filter='...[origin/main]'
Template 4: Self-Hosted Remote Cache
// Custom remote cache server (Express)
import express from "express";
import { createReadStream, createWriteStream } from "fs";
import { mkdir } from "fs/promises";
import { join } from "path";
const app = express();
const CACHE_DIR = "./cache";
// Get artifact
app.get("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || "default";
const filePath = join(CACHE_DIR, team, hash);
try {
const stream = createReadStream(filePath);
stream.pipe(res);
} catch {
res.status(404).send("Not found");
}
});
// Put artifact
app.put("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || "default";
const dir = join(CACHE_DIR, team);
const filePath = join(dir, hash);
await mkdir(dir, { recursive: true });
const stream = createWriteStream(filePath);
req.pipe(stream);
stream.on("finish", () => {
res.json({
urls: [`${req.protocol}://${req.get("host")}/v8/artifacts/${hash}`],
});
});
});
// Check artifact exists
app.head("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || "default";
const filePath = join(CACHE_DIR, team, hash);
try {
await fs.access(filePath);
res.status(200).end();
} catch {
res.status(404).end();
}
});
app.listen(3000);
// turbo.json for self-hosted cache
{
"remoteCache": {
"signature": false
}
}
# Use self-hosted cache
turbo build --api="http://localhost:3000" --token="my-token" --team="my-team"
Template 5: Filtering and Scoping
# Build specific package
turbo build --filter=@myorg/web
# Build package and its dependencies
turbo build --filter=@myorg/web...
# Build package and its dependents
turbo build --filter=...@myorg/ui
# Build changed packages since main
turbo build --filter='...[origin/main]'
# Build packages in directory
turbo build --filter='./apps/*'
# Combine filters
turbo build --filter=@myorg/web --filter=@myorg/docs
# Exclude package
turbo build --filter='!@myorg/docs'
# Include dependencies of changed
turbo build --filter='...[HEAD^1]...'
Template 6: Advanced Pipeline Configuration
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"inputs": ["$TURBO_DEFAULT$", "!**/*.md", "!**/*.test.*"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"inputs": ["src/**", "tests/**", "*.config.*"],
"env": ["CI", "NODE_ENV"]
},
"test:e2e": {
"dependsOn": ["build"],
"outputs": [],
"cache": false
},
"deploy": {
"dependsOn": ["build", "test", "lint"],
"outputs": [],
"cache": false
},
"db:generate": {
"cache": false
},
"db:push": {
"cache": false,
"dependsOn": ["db:generate"]
},
"@myorg/web#build": {
"dependsOn": ["^build", "@myorg/db#db:generate"],
"outputs": [".next/**"],
"env": ["NEXT_PUBLIC_*"]
}
}
}
Template 7: Root package.json Setup
{
"name": "my-turborepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"test": "turbo test",
"clean": "turbo clean && rm -rf node_modules",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "turbo build --filter=./packages/* && changeset publish"
},
"devDependencies": {
"turbo": "^1.10.0",
"prettier": "^3.0.0",
"@changesets/cli": "^2.26.0"
},
"packageManager": "npm@10.0.0"
}
Debugging Cache
# Dry run to see what would run
turbo build --dry-run
# Verbose output with hashes
turbo build --verbosity=2
# Show task graph
turbo build --graph
# Force no cache
turbo build --force
# Show cache status
turbo build --summarize
# Debug specific task
TURBO_LOG_VERBOSITY=debug turbo build --filter=@myorg/web
Best Practices
Do's
- Define explicit inputs - Avoid cache invalidation
- Use workspace protocol -
"@myorg/ui": "workspace:*" - Enable remote caching - Share across CI and local
- Filter in CI - Build only affected packages
- Cache build outputs - Not source files
Don'ts
- Don't cache dev servers - Use
persistent: true - Don't include secrets in env - Use runtime env vars
- Don't ignore dependsOn - Causes race conditions
- Don't over-filter - May miss dependencies
Resources
Source
git clone https://github.com/wshobson/agents/blob/main/plugins/developer-essentials/skills/turborepo-caching/SKILL.mdView on GitHub Overview
This skill teaches configuring Turborepo for fast monorepo builds using local and remote caches. It covers architecture, core pipeline concepts, and practical templates to implement caching effectively across development and CI/CD. Use it to accelerate workflows, reduce re-builds, and enable distributed caching.
How This Skill Works
Turborepo caching is driven by a turbo.json pipeline that declares dependsOn, cache, outputs, inputs, and persistent settings. The templates demonstrate local and remote caching setups (including Vercel remote caching and a self-hosted server) to share artifacts across teams and environments. By aligning cache keys with inputs and specifying outputs, Turborepo reuses previous work and speeds up subsequent runs.
When to Use It
- Setting up new Turborepo projects
- Configuring build pipelines
- Implementing remote caching
- Optimizing CI/CD performance
- Debugging cache misses
Quick Start
- Step 1: Create a turbo.json configuration (Template 1) with your pipeline and outputs.
- Step 2: Add inputs/outputs and enable caching for stable tasks (lint, typecheck) as needed.
- Step 3: Set up remote caching (Template 3 or Template 4), login/link, and configure CI with TURBO_TOKEN.
Best Practices
- Start with a minimal turbo.json and iteratively add outputs, inputs, and dependencies to refine the cache.
- Use globalDependencies and globalEnv to ensure cache consistency across environments.
- Enable cache for stable tasks (lint, typecheck) and be cautious with dev servers (persistent pipelines).
- Integrate remote caching (e.g., Vercel or self-hosted) in CI to reduce runner load and build times.
- Regularly monitor cache hits/misses and invalidate caches when underlying artifacts change.
Example Use Cases
- A multi-app monorepo (apps/web, apps/docs, packages/ui, packages/config) configuring turbo.json to optimize builds.
- Migrating from another tool (e.g., Lerna) to Turborepo and enabling remote caching to speed up migrations.
- GitHub Actions CI workflow using TURBO_TOKEN and TURBO_TEAM to enable remote caching for all jobs.
- Frontend projects using remote cache with Vercel to share artifacts across teams and environments.
- Self-hosted remote cache with an Express server to.publish and retrieve artifacts across the organization.