How to Run the Typecheck Job Efficiently Across CI Lanes in Freebuff
Run bun run scripts/ci/typecheck-lanes.ts to split the monorepo's type-checking workload across parallel CI lanes, reducing total runtime from ~15 minutes to ~3 minutes.
The Freebuff monorepo uses a lane-based parallelization strategy to keep TypeScript type-checking fast at scale. Instead of one slow tsc process over the entire codebase, the system distributes workspaces across independent runners. This article explains the architecture, shows exact commands, and references the source implementation.
What Are CI Typecheck Lanes?
In Freebuff, a lane is a unit of parallel work that type-checks a subset of workspaces. The scripts/ci/typecheck-lanes.ts utility groups packages into lanes and emits shell commands for each. This design lets GitHub Actions spin up multiple runners simultaneously.
The concept originates from the testing documentation and CI workflow, where lanes prevent the "long tail" of monorepo type-checking.
Where the Typecheck Logic Lives
Per-Workspace Typecheck Scripts
Every package defines its own type-checking entry point in package.json:
// main/sdk/package.json
{
"scripts": {
"typecheck": "tsc --noEmit -p ."
}
}
// main/packages/llm-providers/package.json
{
"scripts": {
"typecheck": "tsc --noEmit -p ."
}
}
// main/evals/package.json
{
"scripts": {
"typecheck": "tsc --noEmit -p ."
}
}
Each workspace has an isolated tsconfig.json. This isolation is critical—tsc never crosses workspace boundaries, so compilation units stay small and cacheable.
The Lane Generator: scripts/ci/typecheck-lanes.ts
This TypeScript script partitions workspaces into lanes and outputs parallel execution commands. The CI workflow references it directly, and you can run it locally to replicate CI behavior.
The script performs three operations:
- Discovers workspaces by reading the root
package.jsonworkspace list. - Groups workspaces into balanced lanes (typically 3-5 lanes depending on runner count).
- Emits shell commands like
bun run -C packages/code-map typecheck &.
From docs/testing.md (around line 80), the lane output resembles:
lane-0: bun run -C packages/code-map typecheck &
lane-1: bun run -C packages/agent-runtime typecheck &
lane-2: bun run -C packages/llm-providers typecheck &
Each line runs in the background, and the controlling process waits for all to complete.
CI Workflow Integration
The .github/workflows/ci.yml job orchestrates the lanes:
- Checks out the repository.
- Installs dependencies with
bun install --frozen-lockfile. - Builds the SDK and binary (prerequisites for type-checking).
- Executes
bun run scripts/ci/typecheck-lanes.tsto run all lanes in parallel across GitHub-hosted runners.
The workflow uses matrix strategy or job parallelism to assign each lane to a separate runner instance.
Running Typecheck Lanes Locally
Full Parallel Run (CI-Equivalent)
# Install dependencies once
bun install --frozen-lockfile
# Execute the lane splitter (spawns background jobs automatically)
bun run scripts/ci/typecheck-lanes.ts
The script handles wait internally. Exit codes propagate—if any lane fails, the script exits non-zero.
Dry-Run to Preview Lane Distribution
node scripts/ci/typecheck-lanes.ts --dry-run
Outputs the command list without executing, useful for debugging lane balancing.
Single Workspace (No Parallelism)
cd packages/llm-providers
bun run typecheck
Direct tsc invocation for focused debugging. Bypasses the lane system entirely.
Manual Parallel Execution
# Background each lane explicitly
bun run -C packages/code-map typecheck &
bun run -C packages/agent-runtime typecheck &
bun run -C packages/llm-providers typecheck &
wait # Blocks until all background jobs finish
Useful for shell scripts or CI systems without native matrix support.
Why This Architecture Is Fast
| Factor | Mechanism | Source Location |
|---|---|---|
| Isolated compilation | Each workspace has its own tsconfig.json and tsc --noEmit -p . invocation |
*/package.json "typecheck" scripts |
| Parallel processes | Lane script spawns multiple bun run typecheck jobs concurrently |
scripts/ci/typecheck-lanes.ts |
| Distributed runners | GitHub Actions runs each lane on separate VMs | .github/workflows/ci.yml |
| Incremental caching | node_modules and bun lockfile cached between CI runs |
CI workflow cache configuration |
The docs/testing.md timing table documents ~3 minute total type-check duration versus ~15 minutes for serial execution.
Configuration Deep-Dive
Adjusting Lane Count
The lane script accepts environment variables or CLI flags to control parallelism:
# Force 4 lanes regardless of default detection
LANES=4 bun run scripts/ci/typecheck-lanes.ts
Check scripts/ci/typecheck-lanes.ts source for the exact parameter interface.
Workspace Discovery
The script reads workspace definitions from the root package.json:
{
"workspaces": [
"sdk",
"packages/*",
"evals"
]
}
New packages are automatically included in lane distribution—no manual registration required.
TypeScript Configuration per Lane
Each workspace's tsconfig.json controls its compilation scope. Key settings for speed:
{
"compilerOptions": {
"noEmit": true,
"skipLibCheck": true,
"incremental": true
}
}
incremental enables .tsbuildinfo caching. skipLibCheck avoids re-checking node_modules types on every run.
Debugging Lane Failures
When a lane fails, the error output includes the workspace path:
# Example failure
packages/agent-runtime typecheck: src/runtime.ts:42:15 - error TS2345
Fix the type error, then re-run that specific workspace:
bun run -C packages/agent-runtime typecheck
No need to re-execute all lanes during iteration.
Summary
- Per-workspace scripts (
"typecheck": "tsc --noEmit -p .") isolate compilation units. scripts/ci/typecheck-lanes.tsdistributes workloads across parallel lanes..github/workflows/ci.ymlorchestrates lane execution on CI runners.- Local execution via
bun run scripts/ci/typecheck-lanes.tsmatches CI behavior exactly. - Performance gains come from isolated
tscprocesses, parallel runners, and incremental caching.
Frequently Asked Questions
How do I add a new package to the typecheck lanes?
Create a package.json with a "typecheck": "tsc --noEmit -p ." script and a valid tsconfig.json. The lane script automatically discovers it via the root workspace glob pattern—no additional configuration needed.
Can I run typecheck lanes with npm or pnpm instead of bun?
The scripts reference bun explicitly. To use another package manager, modify scripts/ci/typecheck-lanes.ts to emit npm run or pnpm run commands, and adjust .github/workflows/ci.yml to use that manager's install and execution commands.
Why does my local lane run take longer than CI?
CI benefits from distributed runners—each lane executes on a separate VM. Locally, all lanes share your machine's CPU. Use LANES=2 or LANES=4 to match your core count, or run single-workspace checks during development.
Where is the timing data for lane performance?
The docs/testing.md file contains a timing table comparing serial versus parallel type-check execution. The ~3 minute figure assumes standard GitHub Actions runner configuration with 3-4 lanes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →