fix(runner): scope the job workspace to the runner instance (#39) #53
fix/workspace-instance-isolation
into main
Closes #39.
The workspace path was <work_dir>/<owner>/<repo>/<slot>, which is only unique within one runner process — loop_runner hands each poller a fixed slot in 1..=parallel. Two runner installations sharing a work_dir (which anvil runner service list exists to support) both have a slot 1, so their jobs resolved to the same directory and git checkout --force rewrote each other’s source tree mid-build.
That matches the reported symptom exactly: a compiler error naming a file that is genuinely present in the job’s own commit, passing on re-run — which reads as a flaky test rather than as the runner serving the wrong tree.
Change: add the runner id as a path component → <work_dir>/<owner>/<repo>/<inst>-<slot>.
Keyed on the runner id, not the job/run id, deliberately: the workspace is reused across jobs to preserve build caches (the same reason there is no git clean here), so it must stay stable for a given runner+slot while differing between runners. The key is a sanitized 8-char prefix, so a malformed id cannot introduce a path separator or traversal, and paths stay short for Windows’ limit.
Worth noting for the record: the originally-reported incident on carl was almost certainly the duplicate-daemon bug (#46) — two daemons with the same runner_id, both slot 1, same path. That case is now prevented outright by the identity lock in #49. This change closes the remaining vector: two different runners sharing a work_dir.
Existing workspaces are orphaned once and re-cloned under the new path.
7 tests, incl. the collision property and a traversal guard. Full suite 419 passing; clippy -D warnings and fmt clean.
Deliberate non-goal: the ticket also floated a lock on the workspace dir. With same-id duplicates prevented (#49) and different-id runners now separated by path, there is no remaining known collision vector, so I kept the change to the structural fix rather than adding cross-platform file locking.
🤖 Generated with Claude Code