Coding Agents & Hooks
Transparently offload heavy test, typecheck, and lint runs from Claude Code and Codex worktrees using hooks, load thresholds, and NDJSON events.
Claude Code worktree containing .spotexec.toml
- 1. Parse shell AST
- 2. Match tool rules
- 3. Check
when_load_above
git, rg, ls, vite dev, --watch, pipes, redirects, SPOTEXEC_LOCAL=1, or idle laptop
spotexec run -- '<cmd>'vitest, jest, playwright, pytest, tsc, eslint, prettier, cargo/go test, mvn/gradle verify
Running multiple coding agents (Claude Code, Codex, or custom harnesses) across parallel Git worktrees is where laptops hit a wall: the agents themselves are lightweight HTTP loops, while the verification tools they spawn (vitest, playwright, tsc --noEmit, eslint, cargo test, go test) consume gigabytes of RAM per worktree.
spotexec integrates directly with coding agents through two mechanisms:
- Transparent command hooks that rewrite heavy verification commands to
spotexec run -- '<cmd>'automatically while leaving fast local commands untouched. - Structured NDJSON telemetry on non-TTY
stderr, keeping the command'sstdoutandstderrclean for agent parsing.
Installing the Claude Code Hook#
# Install into .claude/settings.local.json (per-worktree / gitignored)
spotexec hook install --claude
# Or commit into .claude/settings.json (--shared) or install globally in ~/.claude (--global)
spotexec hook install --claude --shared
spotexec hook install --claude --global
# Dry-run any command string to verify whether the hook offloads or keeps it local
spotexec hook check 'pnpm vitest run'
spotexec hook check 'git status'What Gets Offloaded vs. Kept Local#
The PreToolUse Bash hook inspects the command AST before execution:
Agent invokes Bash("<command>")
│
├─ Project has .spotexec.toml? ── No ──▶ Run locally (no-op)
│
├─ Prefixed with SPOTEXEC_LOCAL=1, or uses pipes / redirects / background `&` / `cd ..`?
│ └─ Yes ──▶ Run locally
│
├─ Matches local-only tools (git, rg, ls, dev servers, --watch)?
│ └─ Yes ──▶ Run locally
│
├─ Matches heavy verification (test suites, tsc, linters, formatters, builds, gradle/mvn verify)
│ AND laptop normalized load >= when_load_above?
│ └─ Yes ──▶ Rewrite to: spotexec run -- '<command>'
│
└─ Otherwise ──▶ Run locally- Offloaded to remote runners: Unit & E2E test suites (
vitest,jest,playwright,pytest,go test,cargo test), typecheckers (tsc), linters & formatters (eslint,prettier,biome,ruff,clippy), compilers/bundlers, and Maven/Gradleverifygoals. - Kept local automatically:
git,ls,rg,fd, interactive dev servers (next dev,vite dev), watch modes (--watch), shell pipelines, file redirects, background jobs, commands thatcdoutside the repository, and any command prefixed withSPOTEXEC_LOCAL=1.
Permission Rules in Claude Code#
The hook never auto-approves commands—it only rewrites the command string before Claude Code evaluates permissions. If you pre-approve test commands in Claude Code, add Bash(spotexec run:*) to your allowed tool list so rewritten commands run without manual prompts.
Load-Based Offloading & [hook] Tuning#
Configure [hook] in .spotexec.toml to offload commands only when your laptop is actually under load:
[hook]
when_load_above = 0.6 # offload only when normalized CPU load exceeds 60%
patterns = ["^pnpm build:"] # additional regexes to offload
exclude = ["test:unit:fast"] # regexes to always keep local
# mode = "block" # deny direct execution with instructions instead of rewriting
# disabled = false # disable the hook in this repositoryCodex and Other Agent Harnesses#
For harnesses without a PreToolUse command-rewriting hook (such as Codex CLI), spotexec generates a concise, machine-targeted section in AGENTS.md instructing the agent when to prefix commands with sx and how to interpret exit code 125 vs test failures:
spotexec hook install --codex # appends spotexec instructions to AGENTS.md
spotexec hook install --uninstall # cleanly removes Claude or Codex hook configurationMachine-Readable NDJSON Telemetry#
Whenever stderr is not a TTY—or when --json or SPOTEXEC_AGENT=1 is set—spotexec replaces the interactive terminal spinner with compact, deterministic NDJSON lifecycle events on stderr. The remote command's own output streams untouched:
{"event":"slot_acquired","connect_ms":13,"runner":"aws","slot":3,"waited_ms":0}
{"event":"sync_in","bytes":0,"duration_ms":0,"files":0,"mode":"fingerprint_hit"}
{"event":"sync_out","conflicts":[],"duration_ms":31,"mutated_files":["src/index.ts"]}
{"event":"result","duration_ms":4120,"exit_code":0,"timed_out":false}Full lifecycle event types emitted for agents and log pipelines (--events-file):
- Queueing & placement:
queued,queue_position,placed,slot_acquired - Elastic scaling:
scale_out,provision,instance_running,instance_ready - Execution pipeline:
manifest,sync_in,deps,image,exec,sync_out,pull,cancel,result - Spot resilience:
spot_interruption,spot_rebalance,retry - Diagnostics:
warning(with stable codes:SYNC_CONFLICT,RUNNER_LOST,SPOT_INTERRUPTION,TIMEOUT,LOCAL_FALLBACK) anderror(SPOTEXEC_ERROR, paired with exit code125).