Skip to content

Commit bdd59ba

Browse files
committed
fix(workspace): reconcile ambiguous remote ownership
1 parent 0364ddb commit bdd59ba

5 files changed

Lines changed: 636 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Streamed artifact-collection scripts through SSH stdin so multiple artifact globs no longer overflow macOS OpenSSH multiplexed session requests.
1515
- Allowed Windows and WSL2 SSH readiness checks enough time for delayed native OpenSSH handshakes without slowing Linux or macOS readiness.
1616
- Kept WSL2 workspace-owner commands below the Windows command-line limit by streaming their POSIX scripts over SSH stdin.
17+
- Reconciled lost workspace-owner acquire and renew responses within bounded ownership deadlines, and fenced native Windows child trees with an identity-checked Job Object supervisor.
1718
- Fenced Linode heartbeats and Tailscale metadata updates with exact account-, scope-, and instance-bound claims, preserving legacy instance claims, idle-timeout intent, and safe release continuity.
1819
- Made two timing-sensitive tests robust on loaded CI runners.
1920
- Required exact, locked resource ownership claims before Tencent Cloud, Nebius, Vast, Orgo, Upstash Box, Coder, and EC2 Mac host lifecycle mutations, preventing name-matched, stale, cross-namespace, or concurrently renewed resources from being destroyed.

docs/commands/run.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,16 @@ exclusive and bypass this owner.
221221
Ownership is fenced with a random token and renewed while the lifecycle is
222222
active. If the local client disappears, Crabbox recovers an expired owner only
223223
after verifying that its witnessed remote child is no longer alive. Ambiguous
224-
renewal, release, token, or child state fails closed instead of risking a
225-
concurrent checkout. POSIX, WSL2, and native Windows targets implement the same
226-
protocol; the small sync-finalization lock remains nested inside it.
224+
acquire responses are reconciled within the configured wait, and ambiguous
225+
renewals are retried only through the last confirmed ownership TTL. Release,
226+
token, or child-state uncertainty still fails closed instead of risking a
227+
concurrent checkout.
228+
229+
Native Windows runs additionally place the remote process tree in a Job Object
230+
owned by an identity-checked supervisor. Removing or changing its published
231+
child record closes the job before the workspace can be reused. Linux, macOS,
232+
and WSL2 retain the two-line PID/start-identity witness for the direct remote
233+
child; they do not claim kernel-backed descendant containment.
227234

228235
Use `--full-resync` (alias `--fresh-sync`) when a warm lease smells stale:
229236
Crabbox deletes the remote workdir, skips the fingerprint fast path, reseeds Git

internal/cli/run_workspace_owner_cleanup_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ func (r *runCleanupWorkspaceOwnerTransport) acquire(ctx context.Context, target
110110
stop: make(chan struct{}),
111111
done: make(chan struct{}),
112112
}
113+
owner.confirmedAt(time.Now())
113114
ticks := make(chan time.Time, 1)
114115
go owner.renewLoopWithTicks(ticks, time.Minute)
115116
ticks <- time.Now()
@@ -232,7 +233,7 @@ func TestRunCommandDestructiveCleanupQuiescesWorkspaceOwner(t *testing.T) {
232233
wantOwnerRelease bool
233234
}{
234235
{name: "successful evidence-backed run", command: "renewal-cleanup-success", download: true, wantStop: true},
235-
{name: "renewal fails before stop", command: "renewal-cleanup-success", renewErr: errors.New("renew response lost"), wantExit: 7, wantOwnerRelease: true},
236+
{name: "ambiguous renewal resolves through confirmed stop", command: "renewal-cleanup-success", renewErr: errors.New("renew response lost"), wantStop: true},
236237
{name: "stop is not confirmed", command: "renewal-cleanup-success", stopErr: errors.New("stop not confirmed"), wantExit: 7, wantStop: true, wantOwnerRelease: true},
237238
{name: "remote command remains nonzero", command: "renewal-cleanup-exit-23", wantExit: 23, wantStop: true},
238239
}
@@ -341,7 +342,7 @@ func TestRunCommandDestructiveCleanupQuiescesWorkspaceOwner(t *testing.T) {
341342
}
342343
}
343344

344-
func TestRunCommandRetainedLeaseRetainsFailClosedRenewal(t *testing.T) {
345+
func TestRunCommandRetainedLeaseResolvesAmbiguousRenewalOnRelease(t *testing.T) {
345346
tests := []struct {
346347
name string
347348
args []string
@@ -375,7 +376,7 @@ func TestRunCommandRetainedLeaseRetainsFailClosedRenewal(t *testing.T) {
375376
remote.unblockInspect()
376377
remote.unblockRenewal()
377378
runErr := result.wait(t)
378-
assertRunCleanupExitCode(t, runErr, 7, stdout.String(), stderr.String())
379+
assertRunCleanupExitCode(t, runErr, 0, stdout.String(), stderr.String())
379380
select {
380381
case <-releaseStarted:
381382
t.Fatal("retained lease was destructively stopped")

0 commit comments

Comments
 (0)