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.
sx decides whether to apply remote formatter or snapshot
mutations locally.git/spotexec/conflicts/<run-id>/.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 syncingsx 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#
| Flag | Meaning |
|---|---|
--timeout D | Wall-clock limit for the command (default 5m, or project timeout). Exits 124 if exceeded. |
--queue-timeout D | Maximum wait for a free slot when all runners are saturated (default 10m). |
--sync auto|in|none | auto syncs in and writes mutations back; in syncs in and discards remote changes; none skips sync. |
--writeback always|on-success|never | Controls when remote file changes return (always is default so eslint --fix exiting 1 still writes fixes back). |
-r, --runner NAME / --pool A,B | Pin execution to a specific runner or comma-separated pool. |
--exclusive | Claim all slots on a machine so a heavy build or benchmark gets 100% of its vCPUs and RAM. |
--priority N | Queue priority when waiting for a busy pool (higher numbers dequeue first). |
--no-boot | Use only currently running machines; never launch new Spot VMs for this command. |
--pull GLOB | Pull matching gitignored artifacts back after the run (repeatable, e.g. --pull coverage/). |
--no-deps | Skip [deps] dependency cache hydration. |
--no-retry | Disable automatic replay if an ephemeral Spot VM is interrupted mid-run. |
--fallback-local | Run locally if no remote runner can be reached (never triggers once remote execution has started). |
--force-large-sync | Override the safety cap of 100,000 files or 5 GiB. |
--json / --pretty / -q, --quiet | Telemetry output format on stderr: NDJSON, erasable spinner + footer, or silent. |
--events-file PATH | Append NDJSON lifecycle telemetry to PATH alongside normal terminal output. |
-C, --dir DIR | Execute 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):
| Variable | Value |
|---|---|
SPOTEXEC=1, CI=true | Indicates non-interactive remote execution. |
SPOTEXEC_RUNNER | Name of the active runner (homelab, aws, etc.). |
SPOTEXEC_WORKSPACE | Absolute path to the slot's workspace directory on the runner. |
SPOTEXEC_SLOT | Host-wide slot number (1..max_slots), unique across all repos on that host. |
SPOTEXEC_PORT_BASE | 20000 + slot × 1000 — bind ephemeral test servers/databases here so parallel slots never collide on ports. |
SPOTEXEC_CPUS | Fair CPU share for this slot (vCPUs ÷ max_slots, or all vCPUs under --exclusive). |
SPOTEXEC_SLOTS | Total configured slots on the machine. |
SPOTEXEC_RUN_ID, SPOTEXEC_ATTEMPT | Stable run UUID across Spot replays (SPOTEXEC_ATTEMPT=2 on replay). |
COMPOSE_PROJECT_NAME | sx_<repo>_sNN, isolating concurrent Docker Compose test stacks per slot. |
FORCE_COLOR, CLICOLOR_FORCE | Enabled automatically when your local stdout is an interactive TTY. |
Exit Codes#
| Exit Code | Meaning |
|---|---|
| Command's exit status | Passed through verbatim when the remote command runs and exits normally. |
124 | Remote command exceeded --timeout (sent SIGTERM, then SIGKILL after grace_period). |
125 | spotexec 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 + N | Remote command terminated by signal N. |
130 | Interrupted locally (Ctrl-C, SIGTERM, or SIGHUP). |
1, 2 | CLI 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:
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 Now | Write-back Outcome |
|---|---|
| Equals remote output | Nothing to write. |
| Equals synced baseline | Applied atomically via temp file + rename in the target directory. |
| Changed since sync started | Your 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/.spotexecignoreand do not already exist locally—sodist/,target/, andnode_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:
# 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 ./artifactsTo 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.