Skip to content

fix(sandbox): flag single-task kill()/killAll() as intentional#4515

Open
pedrofrxncx wants to merge 1 commit into
mainfrom
fix/sandbox-task-kill-intentional-flag
Open

fix(sandbox): flag single-task kill()/killAll() as intentional#4515
pedrofrxncx wants to merge 1 commit into
mainfrom
fix/sandbox-task-kill-intentional-flag

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Source

A bug found reading packages/sandbox/daemon/process/task-manager.ts (in the recently-changed hunting ground).

The bug

TaskManager.kill(id, signal) (backs POST /_sandbox/tasks/:id/kill) and killAll() (backs POST /_sandbox/tasks/kill-all) never set task.intentional = true, unlike killByLogName({ intentional: true }) (backs POST /_sandbox/exec/:name/kill, the dev-server Stop button). The intentional field's own docs say it should cover "orchestrator-driven stop, replace-by-logName, or user Stop" — but the generic single-task/kill-all Stop routes are the only callers of kill()/killAll(), so every call through them is exactly that "user Stop" case, and it was never flagged.

Failure scenario

SetupOrchestrator's onTaskExit handler (setup/orchestrator.ts:81-106) treats any non-intentional exit of a WELL_KNOWN_STARTERS task (the dev script) as a crash: it sets status: "error" and transitions the lifecycle to start-failed, which surfaces as a stuck error banner requiring manual retry. If a dev-script task is stopped via the generic /tasks/:id/kill or /tasks/kill-all routes instead of the dev-server-specific /exec/:name/kill route, this deliberate stop gets misclassified as a crash.

Fix

Set t.intentional = true in both kill() and killAll(), matching the existing killByLogName behavior.

Regression test

Added two tests to task-manager.test.ts asserting intentional === true after kill() and after killAll() — both fail on the old code (flag stays undefined/false) and pass with the fix.

Verification

  • bun test packages/sandbox/daemon/process/task-manager.test.ts — 11 pass, 0 fail (including the 2 new tests)
  • cd packages/sandbox && bunx tsc --noEmit — clean
  • bun run fmt — clean

Full CI will run the broader suite.


Summary by cubic

Flagged TaskManager.kill() and killAll() as intentional stops so user-initiated stops aren’t misread as crashes. Aligns behavior with killByLogName() and fixes erroneous error states in the orchestrator.

  • Bug Fixes
    • Set t.intentional = true in kill() and killAll() (routes: POST /_sandbox/tasks/:id/kill, POST /_sandbox/tasks/kill-all) to prevent dev-script tasks from being marked as crashed by the orchestrator.
    • Added tests to packages/sandbox/daemon/process/task-manager.test.ts to assert intentional === true after kill() and killAll().

Written for commit 27a300e. Summary will update on new commits.

Review in cubic

TaskManager.kill(id) and killAll() never set task.intentional, unlike
killByLogName({intentional: true}). Both are only ever called from
deliberate Stop routes (POST /_sandbox/tasks/:id/kill and
/tasks/kill-all), so a stopped WELL_KNOWN_STARTERS (dev script) task
would have its exit misread by SetupOrchestrator's onTaskExit as a
crash, wedging the daemon into status=error/phase=start-failed instead
of respecting the deliberate stop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant