Skip to content

fix(core): report removed running shells as killed - #56982

Merged
rekram1-node merged 2 commits into
devfrom
shell-remove-exited
Oct 19, 2026
Merged

rekram1-node merged 2 commits into
devfrom
shell-remove-exited

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Removing a running shell published shell.deleted, and the killed process later produced a shell.exited for the same ID. The two raced: in some runs exited arrived after deleted, in others before. The removed ID was also pushed onto the retention queue, where it took one of the 25 exited-shell slots and could evict a real finished shell.

 remove while running
-  created -> deleted -> exited(exited)    (or exited first; the order was racy)
+  created -> exited(killed) -> deleted

Change

Every shell now reports exactly one shell.exited, always before any shell.deleted:

remove(shell):
  fail waiters, stop timer, delete output file      # unchanged
  if shell is still running:
    publish shell.exited { status: "killed" }        # new
  publish shell.deleted

after the process ends and output is written:
  if shell was removed: return                      # new: removal already reported it
  set status, resolve waiters, publish shell.exited, retention

killed already exists in Shell.Status and was previously unused. Shell.result already reports a removed shell as killed, so the event now matches it.

In-repo clients are unaffected: the shared client store drops the shell on either exited or deleted, and nothing in core subscribes to these events. External subscribers now see exited(killed) for removed running shells, in a stable order.

Out of scope

  • Shells and their children survive a hard crash of the server, since nothing reaps them on restart.
  • Background & jobs outlive the shell that started them.

Verification

Event stream per lifecycle, from a local scratch subscriber against real processes:

Lifecycle dev this branch
normal exit created → exited same
timeout created → exited(timeout) same
remove after exit created → exited → deleted same
remove while running (4 runs) created → deleted plus exited, in either order created → exited(killed) → deleted, every run
remove a SIGTERM-ignoring process created → deleted → exited created → exited(killed) → deleted
  • Core shell, session-shell, shell-tool and tool-event suites: identical results to dev (3499 pass; the same 7 bash-syntax failures on macOS also fail on dev).
  • bun typecheck in packages/core is clean.

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works. I reproduced it with a temporary test against real processes (a plain sleep and one that ignores SIGTERM): removing a running shell gave created → deleted → exited on dev and created → deleted with this change. The core shell, retention, cleanup, session-shell and tool-shell tests pass, and packages/core typechecks cleanly.

The ownership check is the right guard: removeCommand already fails the waiters, stops the timer, deletes the output file and publishes the deletion, so skipping the outcome step loses nothing. It also keeps removed IDs out of the retention queue.

Public API: the event schemas, HTTP API and SDK are unchanged. The only change is the event sequence: a shell removed while running no longer emits shell.exited. Outside subscribers that waited for shell.exited after removing a shell should treat shell.deleted as the final event.

No concerns.

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