# Running Commands & Write-back

```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.

> [!IMPORTANT]
> `spotexec` only syncs an enclosing Git worktree or a directory containing `.spotexec.toml`. Running `sx` in an untracked parent directory like `$HOME` is refused so `~/.ssh` or `~/.aws` can never be uploaded by mistake.

## 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` / `.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.
