fix(notebook): ignore stale executor callbacks - #609
Merged
Conversation
Bind executor lifecycle callbacks to the owning Session Aggregate generation so replaced executors cannot mutate or persist state for a successor.
Codex ReviewVerdict: mergeable No actionable findings. Summary: Static inspection found no concrete merge-blocking defect introduced by this pull request. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Notebook executor callbacks can arrive after their executor or same-ID Session Aggregate has been replaced. The callback previously resolved ownership from the current registry state, so an old executor could persist
run.json, mutate a replacement Aggregate, or emitonNotebookChangedfor a generation it no longer owned. A teardown/recreate gap could also fall through to the legacy no-live-session persistence path.Proposed change
Bind lifecycle callbacks to the originating Aggregate and executor generation, and close callback admission synchronously before teardown or replacement.
flowchart LR Executor["Executor callback"] --> Token["Aggregate + executor generation"] Token --> Gate{"Still current?"} Gate -->|yes| Queue["Lifecycle queue"] Queue --> Persist["Update Aggregate + run.json + notification"] Gate -->|no| Drop["Ignore stale callback"] Teardown["Teardown / replacement"] --> Close["Close admission synchronously"] Close --> Drain["Drain already-admitted persistence"] Drain --> Replace["Publish successor"]Scope and non-goals
Acceptance criteria and validation
All checks below ran after the final material edit and an independent fixed-SHA closure review confirmed the evidence mapping with 0 actionable findings.
npm test -- --run src/main/notebook/runtime-service.test.ts src/main/notebook/session-aggregate.test.tsnpm run typechecknpm run lintnpm testgit diff --checkReview focus
Please focus on synchronous admission closure, draining already-admitted callback persistence before owner replacement, and the distinction between generated stale callbacks and generation-less legacy rehydration.
Remaining risk: real
NotebookKernelExecutortimer/shutdown timing is represented by deterministic injected-lifecycle tests rather than a non-skipped external E2E. Queue draining intentionally allows teardown to wait for already-admitted repository persistence so it cannot write across generations.Merge by squash only after all required CI and AI review checks pass.