RCH — Remote Compilation Helper
SkillDev toolsOffload cargo/gcc/bun builds to remote workers. Use when compilation slow, "[RCH] local" in stderr, workers unhealthy, hook silent, sync fails, disk pressure, or SSH/daemon/telemetry recovery.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the RCH — Remote Compilation Helper skill
What this skill tells your AI
The instructions your AI receives, as published by dicklesworthstone/remote_compilation_helper in .claude/skills/rch/SKILL.md and read by ahel’s review.
rch transparently offloads compilation commands to remote workers via a Claude Code PreToolUse hook (and, for every other agent/shell, via the managed cargo shim). The daemon picks a healthy worker, rsync's the workspace, runs the build, syncs artifacts back, and exits with the worker's exit code.
This skill is the operational layer agents use when that pipeline isn't working — and the much more common case where it thinks it's working but is silently building locally. Single principle: self-resolve before asking the human. Every recovery path here is one the agent can run alone.
Harmonized against remote_compilation_helper main @ b5c0566 (2026-08-25), workspace v1.0.58. The released 1.0.58 binary predates the post-release shim toolchain-wrapper commits; where that matters it is called out.
Read This First
When a build feels slow, run one thing:
RCH_VISIBILITY=verbose <your-command> 2>&1 | grep -E '^\[RCH\]'
The summary line is a contract:
| Pattern | What to do |
|---|---|
[RCH] remote <worker> (...) | Healthy. Done. |
[RCH] remote <worker> failed (exit N) / failed [RCH-Exxx] ... | Real build/env failure. rch error explain RCH-Exxx; references/ERROR_CODES.md. |
[RCH] local (<reason>) | Fail-open. references/FAIL_OPEN.md; look the reason up verbatim. |
[RCH] remote required; refusing local fallback (<reason>) | Fail-closed (strict remote / shim / dispatcher role). Exit 103 = retry after fixing the fleet; exit 1 = permanent. |
no [RCH] line at all | Hook didn't fire, or the shim isn't ahead on PATH. scripts/protocol_test.sh "<cmd>" then rch shim status. |
If you can't see why offload isn't happening, prove the path in isolation, loudly:
RCH_REQUIRE_REMOTE=1 RCH_VISIBILITY=verbose rch exec -- cargo check --workspace --all-targets
[RCH] remote <worker> (...) ⇒ the offload pipeline is healthy and the problem is upstream (hook classifier, PATH/shim order, the invocation form). A refusal line names exactly what's missing. Then references/RECOVERY_PLAYBOOKS.md.
Quick Start
rch check # exit 0 ready / 1 degraded / 2 not ready
rch status --workers --jobs # full status
rch status --remediation # bands: operator-action vs self-healing vs normal fail-open
rch status --fleet # desired vs live workers: bypassed / disabled / unreachable / missing
rch workers probe --all # connectivity + component matrix + probe_warnings
rch hook status && rch agents status # which agents are wrapped
rch shim status # cargo shim: PATH order, local builds running, toolchains wrapped
rch admit "cargo test -p foo" # read-only admission preflight
rch diagnose --dry-run "cargo check --workspace --all-targets"
RCH_REQUIRE_REMOTE=1 rch exec -- cargo check --workspace --all-targets
rch self-test --all # end-to-end verify
rch doctor --reliability --scope topology,pressure # (`ownership` scope is post-1.0.58)
rch doctor --fix --dry-run # what doctor would auto-fix
rch --robot-triage --json # agent mega-command
If rch status shows storage pressure, check both / and /tmp on the worker before deciding what to fix:
ssh <user>@<host> 'df -h / /tmp /data 2>/dev/null; free -h; cat /proc/pressure/memory /proc/pressure/io'
Fast Triage Order
Run in order; stop at the first failing stage.
- Availability —
rch check,rch status --workers --jobs,rch status --fleet,rch workers probe --all,rch queue - Config + socket consistency —
rch config show --sources,rch --json config get general.socket_path,rch --json daemon status(version is inrch --json status→.data.daemon.daemon.version) - Interception —
rch hook status,rch agents status,rch shim status,rch hook install(idempotent) - Classification + admission + closure —
rch diagnose --dry-run "<cmd>",rch admit "<cmd>" - Remote compile proof —
RCH_REQUIRE_REMOTE=1 rch exec -- cargo check --workspace --all-targets - If sync fails or storage looks bad —
rch cache status --workers <id>,rch gc --dry-run --workers <id>, thenreferences/DISK_AND_PRESSURE.md
Quick Fixes
| Symptom | Command |
|---|---|
| Hook not installed | rch hook install && rch hook status (Gemini/Codex: rch agents install-hook gemini-cli|codex-cli) |
| Codex / scripts / CI build locally | rch shim install on the dispatcher — references/SHIM.md |
rch shim status shows local rustc running | fix PATH order (~/.rch/shims first) or re-run rch shim install after a rustup toolchain install |
| Daemon not running | rch daemon start |
| Daemon version drift after upgrade | rch daemon restart -y — after rch --json queue | jq '.data.active_builds|length' is 0: restart interrupts active builds (-y only skips the warning prompt) |
| Socket mismatch / stale daemon state | rch daemon restart -y then rch --json daemon status |
| No workers configured | rch workers discover --add --yes && rch workers setup --all (or rch workers init) |
| Workers unreachable | rch workers probe --all; keys → references/SSH_KEY_RECOVERY.md |
| "all workers unreachable" persists though probes pass | workers are in temporary bypass: rch status --fleet; they auto-rejoin after N healthy probes + a canary build; rch workers enable <id> clears the record |
| All workers busy | queueing is default (waits 330 s); raise it — RCH_DAEMON_WAIT_RESPONSE_TIMEOUT_SECS=900 <cmd> — or raise total_slots |
no admissible workers | rch admit "<cmd>" — per-candidate rejection reasons + next action |
Requested worker refused (RCH_WORKER=x) | the [RCH-Innn] line carries a next_action; do it — rch never silently swaps workers |
Shell-wrapped cargo refused [RCH-E301] | never rch exec -- bash -lc "cargo … && cargo …"; run separate rch exec -- cargo … |
Job exits 102 with RCH-E309 | a declared --result-dir was missing/partial on the worker; fix the path, the job itself may have passed |
| Need a per-command priority bump | RCH_PRIORITY=high <cmd> |
Want RUSTFLAGS/RUST_LOG forwarded | RCH_ENV_ALLOWLIST=RUSTFLAGS,RUST_LOG <cmd> (CARGO_TARGET_DIR/TMPDIR are always rewritten on the worker) |
Transfer churn / payload too large (transfer skipped) | .rchignore or [transfer] exclude_patterns, then rch daemon reload |
| Path dependency missing remotely | references/PATH_DEPENDENCIES.md ([path_topology]; rch sync --force for stale worker caches) |
Sync Permission denied under /data/projects/<repo> | ssh <user>@<host> "stat -c '%U:%G %a %n' /data/projects/<repo>" then fix: ssh <user>@<host> 'sudo chown -R <user>:<user> /data/projects/<repo> && sudo chmod 775 /data/projects/<repo>' (a fleet-wide rch doctor --reliability --scope ownership probe exists on main only) |
| Worker disk pressure (RCH-E210/211…) | rch cache status, rch gc --dry-run, then references/DISK_AND_PRESSURE.md / sbh |
| Telemetry / SpeedScore broken | references/TELEMETRY_RECOVERY.md |
| Hook says installed but isn't intercepting | scripts/protocol_test.sh "<cmd>" |
| Multiple agents racing on fleet ops | scripts/multi_agent_safety.sh <cmd> + Agent Mail reservations |
--target *-pc-windows-msvc / *-apple-* won't offload | needs a worker declaring os = "windows" / "darwin" — references/WORKERS.md; without one it correctly runs local |
| Cross-platform worker taking the wrong jobs | it has no os = set; add it, and roll rchd out first (older builds ignore the key silently) |
| Build must run locally on purpose (release binary for this host) | .rch/config.toml → [general] force_local = true, plus RCH_CARGO_WRAPPER_BYPASS=1 if the shim is installed; inline RCH_ENABLED=0 does not bypass the hook |
| Need a runbook, not a guess | rch doctor --runbook-list; rch doctor --runbook RCH-R006 |
| Need full environment diagnosis | rch doctor --json, rch config doctor |
Worker Priority and Slots Are Measured, Never Guessed
Rule: never hand-set priority or slots in workers.toml from intuition, core count, or price tier. Derive them from a persisted SpeedScore. If no score exists, fixing that comes first — it is a bug, not a reason to guess.
Hand-tuned priorities silently invert. A real example from this fleet: the two highest-priority workers (110) had the fewest slots (4 and 5) while ten-slot workers sat at 100, so the scheduler preferentially routed to the smallest, most disk-constrained boxes — which is exactly why those two filled to 96–98% first.
The correct sequence
rch workers benchmark --all --force # measure
rch workers list --speedscore # confirm scores persisted and DIFFER
rch workers compare <id> <id> <id> # side-by-side
rch speedscore <worker> # per-worker detail + components
# only now: set priority ∝ measured score, slots ∝ cores
Verify the score is real before you trust it
| Check | Bad sign | Meaning |
|---|---|---|
| Spread across workers | every worker scores the same (esp. exactly 100.0) | scoring is saturated/clamped — worthless for ranking |
| Correlation with hardware | a 6-core box outscores a 16-core box | benchmark is single-threaded or measuring current load |
| Persistence | rch speedscore <w> says "not benchmarked yet" right after a successful benchmark | score isn't being stored; the scheduler will re-benchmark forever |
That third one has a loud tell: daemon.log fills with alternating Benchmark completed successfully ... score: 100.0 and Enqueued benchmark request ... reason: new_worker for the same worker. That is an infinite re-benchmark loop and will grow the log to hundreds of MB. (A persisted score is now treated as "already benchmarked" — if you still see the loop, the store is broken.)
rch workers benchmark (the CLI probe) times an SSH round-trip around a hello-world compile. It is not a capability measurement and must never be used to rank workers. The authoritative number is the daemon's SpeedScore (CPU 30 / disk 20 / compilation 20 / memory 15 / network 15 via rch-telemetry).
Before recommending more hardware
rch queue # → "● N available ● M busy ● 0 offline / X / Y slots free"
If slots are free and nothing is queued, more machines will not help — say so. Look for the real constraint in this order: disk pressure (workers >90% degrade and fail builds long before slots run out; rch status --remediation shows the tightest free-disk ratio), then stale/inverted priorities, then slot counts that don't match core counts. Only recommend buying when rch queue shows sustained zero free slots under real load.
Anti-Asking Rules
These are the questions agents historically ask the human that they should just answer themselves. Do not ask. Do.
- "Can I restart the daemon?" — Yes, once
rch --json queue | jq '.data.active_builds|length'is 0.rch daemon restart -ydoes not drain:-yskips the "N active build(s) will be interrupted" prompt, and the daemon may refuse (shutdown_blocked) while builds run. Wait orrch cancelfirst. - "Can I install the hook / the shim?" — Yes on a dispatcher box (
rch hook install,rch shim install). The shim refuses on arole = "worker"box by itself. - "The autostart cooldown is blocking me — can I delete the cooldown file?" — Don't; wait
auto_start_cooldown_secs(30 s) or runrch daemon start, which isn't gated by the hook cooldown. - "Can I drop the corrupt telemetry db?" — Yes.
references/TELEMETRY_RECOVERY.md. Telemetry is derived data. - "Should I recover SSH keys from a sibling host?" — If the keys are missing here but reachable on another host, yes.
references/SSH_KEY_RECOVERY.mdStep 3. - "Should I run the benchmarks before setting priorities?" — Yes, always. Priorities without a persisted SpeedScore are guesses.
- "Every worker scored the same — can I rank them by core count?" — No. An identical score across the fleet means scoring is broken; fix the score.
- "Should we buy more workers?" — Answer from
rch queueandrch status --remediation, not intuition. Free slots + empty queue = no. Check disk pressure first. - "The worker is at 97% disk — should I delete the rch build pool?" — Run
rch cache status --workers <id>thenrch gc --dry-run --workers <id>; let the reaper's verdicts (REAPABLEvspooled (kept)vsactive/recent (kept)) decide. Neverrm -rfa.rch-target-*dir by hand whilerch queueshows that project active. - "A shimmed build exited 103 — should I uninstall the shim?" — No. 103 means the fleet had nothing admissible;
rch status --remediation, fix that, retry. - "It's waiting on a source-authority lock — is it hung?" — No. Another invocation holds a shared path-dependency root on that worker; it releases when that Cargo exits.
When in genuine doubt, capture the escalation packet (end of references/RECOVERY_PLAYBOOKS.md) and surface that — not a wall of text — to the human.
rch exec Modes You Should Know (details: references/EXEC_MODES.md)
| Need | Form |
|---|---|
| Proof it ran remotely | RCH_REQUIRE_REMOTE=1 rch exec -- cargo test --workspace (refuses fallback; exit 103 retryable / 1 permanent) |
| Machine-readable outcome | rch --json exec -- cargo test -p foo → one NDJSON envelope: outcome (delivery) + location + remote_exit_code |
| Non-compilation remote work (shards, fuzz, bench) | rch exec --job --result-dir crashes -- ./fuzz.sh (result dirs come back even on failure; missing dir ⇒ exit 102 RCH-E309) |
| Build the committed tree, ignore others' edits | rch exec --base HEAD --clean-overlay --overlay-path src/lib.rs -- cargo test (remote-only by construction) |
| Byte-exact source receipt | rch exec --source-content-receipt -- cargo test --locked → rch.source_content_receipt.v1 |
| Several cargo commands | separate invocations — a shell-wrapped chain is refused (RCH-E301) |
Knobs Worth Knowing (Env Vars)
Every name below is read by 1.0.58 code (references/CONFIGURATION.md has the full list and the ones that are not read).
| Variable | Use |
|---|---|
RCH_VISIBILITY=summary|verbose|none | [RCH] ... summary line per offloaded build. Code default is none — set summary in your harness env. Refusals print regardless. |
RCH_REQUIRE_REMOTE=1 | Fail closed instead of building locally (proof mode). Wins over RCH_FORCE_REMOTE. |
RCH_FORCE_REMOTE=1 | Skip local-time/speedup gating, still fail open. |
RCH_QUEUE_WHEN_BUSY | Wait for a slot instead of local fallback. Default 1. 0 to opt out (benchmarking). |
RCH_DAEMON_WAIT_RESPONSE_TIMEOUT_SECS (alias RCH_DAEMON_RESPONSE_TIMEOUT_SECS) | Max seconds to wait for a queued worker. Default 330 — set it higher, not lower, to wait longer. |
RCH_WORKER=id[,id] (alias RCH_WORKERS, merged) | Request specific worker(s); inadmissible ⇒ refused with RCH-Innn + next action. |
RCH_PRIORITY=low|normal|high | Scheduler hint. |
RCH_ENV_ALLOWLIST=K1,K2 | Forward env vars to the remote build (RUSTFLAGS, RUST_LOG, …). |
RCH_MAX_REMOTE_ATTEMPTS | Remote retry budget (default 3). |
RCH_DISABLE_TARGET_REUSE=1 | Legacy per-job remote target dir instead of the pooled .rch-target-*-pool-*. |
RCH_SYNC_TIMEOUT_MS | Per-attempt source-upload timeout (1000..=3600000); default is payload-aware. |
RCH_COMPRESSION_LEVEL | Transfer compression (default 3). (RCH_TRANSFER_ZSTD_LEVEL is not read.) |
RCH_SSH_SERVER_ALIVE_INTERVAL_SECS / RCH_SSH_CONTROL_PERSIST_SECS | Keepalive (recommend 15) / opt-in ControlMaster (default OFF). |
RCH_LOG_LEVEL=debug | Diagnostics on stderr; surfaces which fail-open path was taken. |
RCH_SOCKET_PATH | Daemon socket override. (RCH_DAEMON_SOCKET is not read.) |
RCH_NO_SELF_HEALING=1 / --no-self-healing / --no-hook-auto-start | Disable autostart/hook re-install for one invocation. |
RCH_ENABLED=0 | Disable rch — only effective in the process env of the hook/rch exec, not as an inline prefix inside an agent's command. |
RCH_CARGO_WRAPPER_BYPASS=1 | Shim/toolchain-wrapper bypass → real cargo. |
RCH_OUTPUT_FORMAT=json|toon, RCH_JSON=1 | Machine output. |
RCH_CANONICAL_PROJECT_ROOT / RCH_ALIAS_PROJECT_ROOT | Override [path_topology] roots. |
RABS (Asupersync-native Accelerated Build Sidecar) — What To Tell Agents
RABS is a new build-caching sidecar inside the rch workspace (11 crates — 10 rabs-* plus rabsd — all publish = false; binaries rabsd, rabs-wrap as a RUSTC_WRAPPER, rabs-wkr). As of 2026-08-25 it is opt-in and shadow-only: rabs-wrap computes a canonical action key, consults the daemon, records a would_have_hit receipt, then execs the real rustc unconditionally — it does not serve cache hits yet (bead bd-k52xe open). Nothing wires RUSTC_WRAPPER automatically; install.sh builds the spine best-effort and scripts/rabs_fleet_deploy.sh deploys it only to bwrap-capable hosts. It is fail-open by design (rabsd --doctor treats a dead daemon as WARN).
Operator surface: rch rabs gc plan|run|history, rch rabs worker reconcile <W> (proposals only), rch rabs doctor, rch rabs inventory. These act on the RABS CAS (~/.cache/rch/rabs-state/cas), not on worker target dirs — don't reach for them for disk pressure. If an agent asks "is RABS making my builds faster?" the honest answer today is no; the win is still rch offload itself.
Reference Index
Recognising what's wrong:
references/FAIL_OPEN.md— every[RCH] local (...)reason and refusal line mapped to a self-fixreferences/ERROR_CODES.md—RCH-Ennncatalog,RCH-Innnrefusal codes,RCH-Rnnnrunbooks, the three exit-code schemesreferences/TROUBLESHOOTING.md— diagnostic flow + common errors
Solving specific failure classes:
references/RECOVERY_PLAYBOOKS.md— symptom → fix in ≤90 s, lettered playbooks A–Preferences/SHIM.md— cargo shim, toolchain wrapping, local-build alarm, building locally on purposereferences/EXEC_MODES.md— strict remote, job mode, clean overlay, receipts, exec envelope, source-authority locksreferences/SSH_KEY_RECOVERY.md— when workers.toml references keys this host doesn't havereferences/PATH_DEPENDENCIES.md— multi-repo workspaces, closure planner,[path_topology]references/DISK_AND_PRESSURE.md— pooled target dirs,rch gc/rch cache, reaper config, RCH-E210..217,sbhhandoffreferences/TELEMETRY_RECOVERY.md— corrupt~/.local/share/rch/telemetry/telemetry.dbreferences/SELF_HEALING.md— autostart cooldown, temporary bypass + auto-rejoin,[self_healing]references/SSH_TUNING.md— ControlMaster, keepalives, retry classification
Operating in fleets and swarms:
references/MULTI_AGENT_CONTENTION.md— TOCTOU, source-authority locks, fleet deploy races, autostart cooldown sharingreferences/OPERATIONS.md— runbook + worker fleet lifecyclereferences/WORKERS.md— worker config, lifecycle states,os =gate, Windows workers, fleet commandsreferences/CONFIGURATION.md— every config section/key/default, env vars (incl. the ones that don't exist), runtime pathsreferences/HOOKS.md— hook protocol, per-agent install, what is/isn't interceptedreferences/MACHINE_INTROSPECTION.md—--jsonshapes, envelope, verified jq recipesreferences/COMMANDS.md— full command/flag reference (quoted from clap)
Automation scripts (in scripts/):
auto_recover.sh— heuristic, dry-run-by-default fleet recoveryworker_disk_triage.sh— read-only mount-aware disk report per workerprotocol_test.sh— probe the hook protocol with synthetic inputmulti_agent_safety.sh— flock wrapper for fleet/setup operationsmine_rch_history.sh— find prior agent sessions that hit a given failurediagnose-rch.sh— end-to-end diagnostic (health, socket, workers, hook, shim, protocol)validate-setup.sh— verify prerequisites/config/daemon/hook before use
Templates and project docs:
assets/workers-template.toml- Source: https://github.com/Dicklesworthstone/remote_compilation_helper —
README.md,CHANGELOG.md,docs/runbooks/*
Adjacent Skills
sbh— disk-pressure defense for AI coding workloads. Use whenRCH-E210/211/215/216fires andrch gcisn't enough.agent-mail— file reservations and messaging between agents. Use beforerch fleet deployor any worker config edit in a swarm.ntm/vibing-with-ntm— multi-agent tmux orchestration; common parent context for agents that hit rch failures.cass— search prior agent sessions;scripts/mine_rch_history.shis the fallback when the cass index has dead pointers.provision-new-machine— provisioning a cloud VPS as an rch worker (root env,TMPDIR-only, no globalCARGO_TARGET_DIR).
Reading Output: TUI vs Hook
rch with no subcommand is the PreToolUse hook (JSON in, JSON out) — don't run bare rch expecting help; use rch --help. rch dashboard (alias rch tui) and rch web are interactive and block your session; rch dashboard --dump-state / --test-mode are the non-interactive escapes.
Signals
- GitHub stars
- 60
- Forks
- 4
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
rch-dicklesworthstone- Source
- github.com/dicklesworthstone/remote_compilation_helper