diff --git a/.yoi/tickets/00001KXDWAXTS/item.md b/.yoi/tickets/00001KXDWAXTS/item.md index 78206adc..26b4c2f2 100644 --- a/.yoi/tickets/00001KXDWAXTS/item.md +++ b/.yoi/tickets/00001KXDWAXTS/item.md @@ -1,8 +1,8 @@ --- title: 'Stop Worker before cleanup deletion' -state: 'inprogress' +state: 'done' created_at: '2026-07-13T13:58:15Z' -updated_at: '2026-07-13T13:58:54Z' +updated_at: '2026-07-13T14:05:35Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-07-13T13:58:54Z' diff --git a/.yoi/tickets/00001KXDWAXTS/thread.md b/.yoi/tickets/00001KXDWAXTS/thread.md index 82d4dc9e..05b460f9 100644 --- a/.yoi/tickets/00001KXDWAXTS/thread.md +++ b/.yoi/tickets/00001KXDWAXTS/thread.md @@ -39,4 +39,45 @@ Ticket を `yoi ticket` が queued にしました。 State changed to `inprogress`. +--- + + + +## Implementation report + +Changed Worker cleanup execution to stop the selected Worker before Runtime deletion. + +Root cause: +- Runtime `delete_worker` rejects active Workers with `worker is running and must be stopped before deletion`. +- The cleanup plan can present a Worker as cleanup-eligible when the browser-facing projected state is not `running` (for example execution rejected/unconnected) while Runtime status is still active. +- Cleanup execution previously called Runtime delete directly, causing `workspace_cleanup_worker_runtime_delete_rejected`. + +Changes: +- `cleanup_runtime_worker_for_execution` now calls `stop_worker` first with a cleanup reason. +- If stop is rejected, the cleanup fails with `workspace_cleanup_worker_runtime_stop_rejected` and preserves Runtime diagnostics. +- Only after accepted stop does cleanup call Runtime delete and then delete the Backend registry row. +- Unknown-worker during stop/delete remains idempotent for stale Backend registry cleanup. + +Manual observation: +- Direct Runtime DELETE for worker 1 returned HTTP 400: `worker 1 is running and must be stopped before deletion`. +- Direct Runtime stop followed by delete succeeded, confirming the required operation sequence. + +Validation: +- `cargo fmt --check` +- `git diff --check` +- `cargo check -q` +- `cargo test -q -p yoi-workspace-server` +- targeted cleanup execution test +- `nix build .#yoi --no-link` + + +--- + + + +## State changed + +State changed to `done`. + + --- diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index 1c767bd9..dbfd0a12 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -2024,6 +2024,28 @@ fn cleanup_runtime_worker_for_execution( runtime_id: &str, candidate: &CleanupWorkerCandidate, ) -> ApiResult<()> { + match api.runtime.stop_worker( + runtime_id, + candidate.runtime_worker_id.as_str(), + WorkerLifecycleRequest { + reason: Some("cleanup worker before deletion".to_string()), + }, + ) { + Ok(result) if result.state == WorkerOperationState::Accepted => {} + Ok(result) => { + return Err(ApiError::with_diagnostics( + Error::RuntimeOperationFailed { + runtime_id: runtime_id.to_string(), + code: "workspace_cleanup_worker_runtime_stop_rejected".to_string(), + message: "Runtime did not stop selected Worker before deletion".to_string(), + }, + result.diagnostics, + )); + } + Err(RuntimeRegistryError::UnknownWorker { .. }) => return Ok(()), + Err(error) => return Err(error.into_error().into()), + } + match api .runtime .delete_worker(runtime_id, candidate.runtime_worker_id.as_str()) @@ -2033,7 +2055,7 @@ fn cleanup_runtime_worker_for_execution( Error::RuntimeOperationFailed { runtime_id: runtime_id.to_string(), code: "workspace_cleanup_worker_runtime_delete_rejected".to_string(), - message: "Runtime did not delete selected Worker".to_string(), + message: "Runtime did not delete selected Worker after stopping it".to_string(), }, result.diagnostics, )),