Guard all four PyCoder result/error handlers against a disposed node#94
Merged
dovvnloading merged 1 commit intoJul 22, 2026
Merged
Conversation
PyCoderNode's result/error handlers were the one worker-result-handler family with no is_disposed guard at all - every sibling (_handle_artifact_result/_handle_artifact_error, _handle_code_sandbox_result/ _handle_code_sandbox_error) already had one. Concretely: manual-mode execution finishing kicks off a SECOND, window-level thread (self.pycoder_agent_thread, a PyCoderAgentWorker with no stop() of its own - an in-flight LLM call can't be cancelled early) to get an AI analysis of the output. If the node is deleted mid-execution or mid-analysis, dispose() has no way to reach or stop this second-stage thread - it only knows about the node's own worker_thread, not the window's separate pycoder_agent_thread attribute. When the analysis result (or an error) later arrives, _handle_pycoder_analysis_result/_handle_pycoder_error used to mutate, reselect, and save-chat a node no longer on the canvas. The AI-driven mode's single-stage result handler (_handle_ai_pycoder_result) had the identical gap - it wasn't specific to the two-stage manual-mode flow. All four handlers (_handle_code_execution_result, _handle_pycoder_analysis_result, _handle_ai_pycoder_result, _handle_pycoder_error) now guard on is_disposed, matching the established sibling pattern exactly. Guarding _handle_code_execution_result additionally stops a pointless second-stage analysis thread from ever starting for a node that's already gone. Full suite 1703 passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dovvnloading
deleted the
hotfix/pycoder-analysis-thread-use-after-dispose
branch
July 22, 2026 02:08
3 tasks
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.
Summary
PyCoderNode's result/error handlers were the one worker-result-handler family with nois_disposedguard at all — every sibling (_handle_artifact_result/_handle_artifact_error,_handle_code_sandbox_result/_handle_code_sandbox_error) already had one.Manual-mode execution finishing kicks off a second, window-level thread (
self.pycoder_agent_thread, aPyCoderAgentWorkerwith nostop()of its own — an in-flight LLM call can't be cancelled early) to get an AI analysis of the output. If the node is deleted mid-execution or mid-analysis,dispose()has no way to reach or stop this second-stage thread — it only knows about the node's ownworker_thread, not the window's separatepycoder_agent_threadattribute. When the analysis result (or an error) later arrives, the handler used to mutate, reselect, and save-chat a node no longer on the canvas. The AI-driven mode's single-stage result handler had the identical gap — it wasn't specific to the two-stage manual-mode flow.Changes
All four handlers now guard on
is_disposed, matching the established sibling pattern exactly:_handle_code_execution_result— additionally stops a pointless second-stage analysis thread from ever starting for a node that's already gone_handle_pycoder_analysis_result_handle_ai_pycoder_result_handle_pycoder_error(shared by all three PyCoder worker stages)Test plan
Nonecases)finishedsignal touches nothing and never starts stage 2Fourth of the fixes from the recent adversarial bug-scan (after #91, #92, #93).