fix(dispatcher): guard eventEmitters.emit() against a torn-down project (aicshud/WHEEL#1027) - #142
Merged
so5 merged 4 commits intoSep 16, 2026
Conversation
onCleanProject() must reconnect SSH for any remotehost with a pending deferred cleanup (left behind by stopProject(), aicshud/WHEEL#1020/#1021) before it can call runDeferredCleanups() - the SSH connection was already unconditionally torn down by stopProject(). - transferrer.js: getDeferredCleanupRemotehostIDs() does not exist yet. - handlers/projectController.js: onCleanProject() does not attempt any SSH reconnection, so a pending deferred cleanup is silently dropped once the project is cleaned instead of resumed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
…s on manual clean (aicshud/WHEEL#1023) stopProject() (aicshud/WHEEL#1020/#1021) unconditionally disconnects every SSH connection on stop, but deliberately leaves deferredCleanupRegistry populated: a not-yet-executed downstream task may still need the preserved remote-symlink target file if the project is resumed. If the user cleans the project instead of resuming it, that entry never gets a chance to run naturally - only runProject()'s own natural-completion path calls runDeferredCleanups() - permanently leaking the preserved remote files. - transferrer.js: add getDeferredCleanupRemotehostIDs(), a non-mutating peek at which remotehosts a project still has pending deferred cleanups for. - handlers/projectController.js: add reconnectAndRunDeferredCleanups(), called from onCleanProject() before cleanProject()'s git reset. It resolves each pending remotehostID's connection info the same way runDispatcher() does, reconnects via createSsh() (which may prompt for a password/passphrase - acceptable here since cleanProject is only ever triggered by an explicit user action), runs the deferred cleanups, then disconnects again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
Same defect class as aicshud/WHEEL#1019: eventEmitters.get(projectRootDir) returns undefined once a project has been fully torn down (eventEmitters.delete(projectRootDir) already ran), so any late-resolving work that still tries to emit on it crashes with "TypeError: Cannot read properties of undefined (reading 'emit')". #1019 fixed this for _setComponentState/setTaskState; a live "TypeError: Cannot read properties of undefined (reading 'emit')" from _dispatchTask was then confirmed on real Fugaku hardware during aicshud/WHEEL#1024's investigation, and auditing the rest of dispatcher.js found the same unguarded pattern in _delegate (two call sites) and _viewerHandler too. Add reproduction tests for the three that are reasonably testable in isolation (_dispatchTask, _delegate's "workflow" branch, _viewerHandler); all three currently reject. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
…ct (aicshud/WHEEL#1027)
Apply the same if (ee) { ee.emit(...) } guard aicshud/WHEEL#1019 added to
_setComponentState/setTaskState to the remaining unguarded
eventEmitters.get(...).emit(...) call sites in dispatcher.js:
- _dispatchTask (emit "taskDispatched") - the one confirmed live on real
Fugaku hardware during aicshud/WHEEL#1024's investigation.
- _delegate, parameterStudy branch (emit "componentStateChanged").
- _delegate, workflow/stepjob branch (emit "componentStateChanged").
- _PSHandler's debounced updateComponentJson (emit "componentStateChanged").
- _viewerHandler (emit "resultFilesReady").
The parameterStudy branch of _delegate and _PSHandler's debounced
callback are fixed by the identical one-line pattern already proven
correct by the other three (now test-covered) sites and by #1019, but
have no dedicated reproduction test here: exercising them for real
requires a full parameterStudy dispatch (scatter/gather files, a
populated parameterSetting.json, nested child dispatch), fixture
infrastructure this test file does not otherwise have and that would be
disproportionate to build solely for this defensive-guard fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
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
Fixes
aicshud/WHEEL#1027:dispatcher.jshad several unguardedeventEmitters.get(projectRootDir).emit(...)call sites, the same defect classaicshud/WHEEL#1019already fixed for_setComponentState/setTaskState. Once a project is fully torn down,eventEmitters.delete(projectRootDir)has already run, soeventEmitters.get(...)returnsundefinedand any late-resolving work that still tries to emit on it crashes withTypeError: Cannot read properties of undefined (reading 'emit').A live occurrence from
_dispatchTaskwas confirmed on real Fugaku hardware duringaicshud/WHEEL#1024's investigation. Auditing the rest of the file found the identical unguarded pattern in four more places.What changed
Applied the same
if (ee) { ee.emit(...) }guard#1019established to:_dispatchTask(emit"taskDispatched") - the one confirmed live._delegate, parameterStudy branch (emit"componentStateChanged")._delegate, workflow/stepjob branch (emit"componentStateChanged")._PSHandler's debouncedupdateComponentJson(emit"componentStateChanged")._viewerHandler(emit"resultFilesReady").Testing
_dispatchTask,_delegate's "workflow" branch,_viewerHandler). The parameterStudy branch of_delegateand_PSHandler's debounced callback are fixed by the identical one-line pattern already proven correct by the other three (and by #1019) but have no dedicated reproduction test: exercising them for real requires a full parameterStudy dispatch (scatter/gather files, a populatedparameterSetting.json, nested child dispatch) - fixture infrastructure this test file does not otherwise have, disproportionate to build solely for this defensive-guard fix.npm run testDocker -w server— full suite green (1665 passing, 15 pending, 0 failing).Related
Filed against
aicshud/WHEEL#1027on GitLab (issue tracker for this project). Builds directly onaicshud/WHEEL#1023(#141, merged) and#1020/#1021/#1022(#140, merged).🤖 Generated with Claude Code
https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu