Intent
Stop orphaning billable Hetzner cloud VMs on any post-Server.Create error or cancellation by deleting the server before returning the error and setting vmID immediately.
Plan reference
Source finding
Cloud VM is orphaned on any post-Create error or cancellation (internal/workspace/hetzner.go:64-103, vm.go:35-47). After Server.Create succeeds, a polling failure returns without setting w.vmID, so VMWorkspace.Destroy is a no-op and the billable server lingers forever.
Scope
- Allowed files (test):
internal/workspace/hetzner_test.go (extend), vm_test.go (extend)
- Allowed files (impl):
internal/workspace/hetzner.go, internal/workspace/vm.go
- Dependencies: none
- Complexity: low — Risk: low
Acceptance criteria
Use slice T09 in the plan as detailed acceptance criteria. Summary:
- Failing test written first, compiles and fails on
main.
- With a fake Hetzner client whose
GetByID always errors after Server.Create returned a server, HetznerProvider.Create returns an error AND the fake records a Server.Delete(server.ID) call. Today the server lingers.
- Impl: in
HetznerProvider.Create, after Server.Create succeeds, defer-on-error a best-effort p.client.Server.Delete(forceCtx, server). In VMWorkspace.Provision, set w.vmID = server.ID immediately after Create succeeds so a caller-initiated Destroy can clean up.
go test ./internal/workspace/... -race green; ./scripts/test-regression.sh green.
Guardrails
- Strict TDD; failing regression test must compile and fail on
main first.
- Worktree flow; own branch, merge via
verify-and-merge.sh.
- Update
.context/harness-reliability/tracker.md on pick-up and merge.
- Deterministic tests only — fake Hetzner client; no real cloud calls.
- Append engineering-log entry on merge.
Intent
Stop orphaning billable Hetzner cloud VMs on any post-
Server.Createerror or cancellation by deleting the server before returning the error and settingvmIDimmediately.Plan reference
docs/plans/2026-06-24-harness-reliability-plan.md— slice T09Source finding
Cloud VM is orphaned on any post-
Createerror or cancellation (internal/workspace/hetzner.go:64-103,vm.go:35-47). AfterServer.Createsucceeds, a polling failure returns without settingw.vmID, soVMWorkspace.Destroyis a no-op and the billable server lingers forever.Scope
internal/workspace/hetzner_test.go(extend),vm_test.go(extend)internal/workspace/hetzner.go,internal/workspace/vm.goAcceptance criteria
Use slice T09 in the plan as detailed acceptance criteria. Summary:
main.GetByIDalways errors afterServer.Createreturned a server,HetznerProvider.Createreturns an error AND the fake records aServer.Delete(server.ID)call. Today the server lingers.HetznerProvider.Create, afterServer.Createsucceeds, defer-on-error a best-effortp.client.Server.Delete(forceCtx, server). InVMWorkspace.Provision, setw.vmID = server.IDimmediately afterCreatesucceeds so a caller-initiatedDestroycan clean up.go test ./internal/workspace/... -racegreen;./scripts/test-regression.shgreen.Guardrails
mainfirst.verify-and-merge.sh..context/harness-reliability/tracker.mdon pick-up and merge.