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:

  1. Discovers workspaces by reading the root package.json workspace list.
  2. Groups workspaces into balanced lanes (typically 3-5 lanes depending on runner count).
  3. 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.ts to 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.ts distributes workloads across parallel lanes.
  • .github/workflows/ci.yml orchestrates lane execution on CI runners.
  • Local execution via bun run scripts/ci/typecheck-lanes.ts matches CI behavior exactly.
  • Performance gains come from isolated tsc processes, 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →