Workflows & Agents
View .md

Running Commands & Write-back

Run flags, runner environment variables, exit codes, 3-way content-ID write-back safety, and pulling gitignored build or coverage artifacts.

3-Way Content-ID Write-Back
How sx decides whether to apply remote formatter or snapshot mutations locally
Remote File Mutated
prettier / eslint / vitest -u
remote SHA-1 != baseline SHA-1
1. Local SHA-1 now == Remote SHA-1
No-op — local file already matches the remote result; zero disk churn.
2. Local SHA-1 now == Synced Baseline SHA-1
Safe to write — remote bytes stage to a temp file and atomically rename into place.
3. Local SHA-1 now != Baseline (edited locally mid-run)
Your local edit is preserved untouched; remote version parks in .git/spotexec/conflicts/<run-id>/.
sh
sx [flags] <command> [args...]        # sync → run remotely → write back changes
sx [flags] -- <command> [args...]     # explicit separator if command matches a flag/subcommand
sx <task> [args...]                   # run a named or parallel task from .spotexec.toml
spotexec exec -- <command>            # run inside the last-used slot without syncing

sx stops parsing its own flags at the first non-flag token, so sx pnpm vitest run --watch=false forwards --watch=false straight to pnpm. Multiple arguments are shell-quoted individually; a single string argument runs as a shell pipeline (sx 'pnpm lint && pnpm test'). Absolute local paths inside your worktree are automatically rewritten to the remote workspace path, and remote stack trace paths are rewritten back on output.

Run Flags#

FlagMeaning
--timeout DWall-clock limit for the command (default 5m, or project timeout). Exits 124 if exceeded.
--queue-timeout DMaximum wait for a free slot when all runners are saturated (default 10m).
--sync auto|in|noneauto syncs in and writes mutations back; in syncs in and discards remote changes; none skips sync.
--writeback always|on-success|neverControls when remote file changes return (always is default so eslint --fix exiting 1 still writes fixes back).
-r, --runner NAME / --pool A,BPin execution to a specific runner or comma-separated pool.
--exclusiveClaim all slots on a machine so a heavy build or benchmark gets 100% of its vCPUs and RAM.
--priority NQueue priority when waiting for a busy pool (higher numbers dequeue first).
--no-bootUse only currently running machines; never launch new Spot VMs for this command.
--pull GLOBPull matching gitignored artifacts back after the run (repeatable, e.g. --pull coverage/).
--no-depsSkip [deps] dependency cache hydration.
--no-retryDisable automatic replay if an ephemeral Spot VM is interrupted mid-run.
--fallback-localRun locally if no remote runner can be reached (never triggers once remote execution has started).
--force-large-syncOverride the safety cap of 100,000 files or 5 GiB.
--json / --pretty / -q, --quietTelemetry output format on stderr: NDJSON, erasable spinner + footer, or silent.
--events-file PATHAppend NDJSON lifecycle telemetry to PATH alongside normal terminal output.
-C, --dir DIRExecute as if invoked from DIR.

Environment on the Runner#

Every remote command receives these environment variables automatically, in addition to keys listed in env_allow (forwarded from your local shell) and env (static map in .spotexec.toml):

VariableValue
SPOTEXEC=1, CI=trueIndicates non-interactive remote execution.
SPOTEXEC_RUNNERName of the active runner (homelab, aws, etc.).
SPOTEXEC_WORKSPACEAbsolute path to the slot's workspace directory on the runner.
SPOTEXEC_SLOTHost-wide slot number (1..max_slots), unique across all repos on that host.
SPOTEXEC_PORT_BASE20000 + slot × 1000 — bind ephemeral test servers/databases here so parallel slots never collide on ports.
SPOTEXEC_CPUSFair CPU share for this slot (vCPUs ÷ max_slots, or all vCPUs under --exclusive).
SPOTEXEC_SLOTSTotal configured slots on the machine.
SPOTEXEC_RUN_ID, SPOTEXEC_ATTEMPTStable run UUID across Spot replays (SPOTEXEC_ATTEMPT=2 on replay).
COMPOSE_PROJECT_NAMEsx_<repo>_sNN, isolating concurrent Docker Compose test stacks per slot.
FORCE_COLOR, CLICOLOR_FORCEEnabled automatically when your local stdout is an interactive TTY.

Exit Codes#

Exit CodeMeaning
Command's exit statusPassed through verbatim when the remote command runs and exits normally.
124Remote command exceeded --timeout (sent SIGTERM, then SIGKILL after grace_period).
125spotexec infrastructure error (unreachable SSH host, bootstrap failure, quota error). Outranks test failures in parallel tasks so coding agents never confuse "couldn't run tests" with "tests failed".
128 + NRemote command terminated by signal N.
130Interrupted locally (Ctrl-C, SIGTERM, or SIGHUP).
1, 2CLI usage or configuration validation error.

3-Way Content-ID Write-back#

When a remote command formats code (prettier --write), applies lint fixes (eslint --fix), updates test snapshots (vitest -u), or runs codegen, spotexec brings those edits back without risking concurrent local work.

Before syncing, sx records the exact Git SHA-1 blob ID of every synced file—the baseline. After execution, spotexec-helper hashes the slot workspace by content (never trusting mtimes) and returns mutations. Each modified or deleted file is checked against your local worktree right now:

diagram
Remote file mutated (new SHA-1 != baseline SHA-1)
  │
  ├─ Local file now == Remote result  ──▶ No-op (already identical)
  │
  ├─ Local file now == Baseline SHA-1 ──▶ Safe to apply: stage to temp file + atomic rename
  │
  └─ Local file now != Baseline SHA-1 ──▶ Edited locally mid-run!
                                          • Keep your local file untouched
                                          • Save remote bytes to <git-dir>/spotexec/conflicts/<run-id>/<path>
                                          • Emit SYNC_CONFLICT warning
Your Local File NowWrite-back Outcome
Equals remote outputNothing to write.
Equals synced baselineApplied atomically via temp file + rename in the target directory.
Changed since sync startedYour local file is preserved. Remote version is parked in <git-dir>/spotexec/conflicts/<run-id>/<path> and a SYNC_CONFLICT warning is emitted.

Additional write-back invariants:

  • Deletions apply deepest-first before file writes.
  • Newly created remote files are written back only if they are not ignored by .gitignore / .spotexecignore and do not already exist locally—so dist/, target/, and node_modules/ never pollute your laptop.
  • Paths containing .git (in any case spelling) or traversing symlinks are rejected unconditionally.

Pulling Gitignored Artifacts#

When you do want gitignored outputs back locally (such as HTML coverage reports, Playwright traces, or compiled binaries), pull them explicitly:

sh
# Pull after a single run
sx --pull coverage/ --pull 'packages/*/coverage' pnpm vitest run --coverage

# Or pull on demand from the most recently used slot
spotexec pull coverage/ playwright-report/ --dest ./artifacts

To pull artifacts automatically on every run, set pull_artifacts = ["coverage/"] under [sync] in .spotexec.toml. Pulled artifacts never overwrite tracked or non-ignored source files.