Skip to main content

How it works

Shims​

~/.turnstile/shims holds symlinks named swift, cargo, npm, and so on, all pointing at one binary. Each works out whether the command is heavy. Quick ones (swift --version, npm install, cargo fmt) exec the real tool in a few milliseconds without touching the daemon.

Heavy commands run in place with the same output, exit code, working directory, environment, and Ctrl-C as the real tool.

The daemon​

The daemon starts on the first gated command, listens on a Unix socket in ~/.turnstile, and exits after 30 idle minutes, or 4 hours after it last saw a build run outside turnstile. There's nothing to launch or keep running.

It fails open. If the daemon is missing or broken, commands run ungated rather than failing. If it crashes, running jobs re-register with a fresh one and waiting jobs requeue, so limits still hold, and anything it had paused carries on.

turnstile restart uses that recovery path deliberately: paused jobs resume, waiting jobs requeue, and running jobs register with the replacement daemon. turnstile stop is the escape hatch instead. It releases waiting jobs to run ungated and leaves running jobs to finish untracked.

What gets gated​

Gated by default: swift, xcodebuild, cargo, go, gradle, make, npm, pnpm, yarn, bun, npx, vitest, jest, playwright, tsc, xcrun, and corepack. The last two are gated by the tool they run, so xcrun swift build counts as swift build and corepack pnpm test as pnpm test.

Each gated command falls into a class, and each class has its own slots:

ClassExamples
compileswift build, cargo build, tsc, npm run build
testswift test, cargo test, npm test
browserplaywright test, npm run e2e

Package scripts are classified by name:

Script namesClass
test, test:unit, citest
e2e, playwrightbrowser
build, lint, typecheckcompile
dev, start, watchpass through

See what any command would do:

turnstile classify npm run build

Override classification per project or machine with commands and scripts.

Duplicate and nested runs​

  • Duplicate runs merge. The same command on an unchanged working tree joins the run in progress and gets its output and exit code. The fingerprint includes HEAD, index state, and each changed or untracked file's size, timestamps, and inode. Root .env files are included even when ignored. A newer request from the same worktree replaces its queued older one.
  • Nested calls pass through. swift test calling swift build doesn't wait on itself.