fix(dispatcher): _getState() reports "stopped" for a genuine external stop (aicshud/WHEEL#1028) - #143
Merged
Merged
Conversation
_getState() never returns "stopped", the documented state for an
externally-stopped project (documentMD/user_guide/_reference/
3_workflow_screen/1_graphview.md), even though start()'s onStop handler
sets stoppedExternally (aicshud/WHEEL#1020) specifically to record that
case. Currently masked at the whole-project level because
onStopProject() unconditionally force-overwrites the project state to
"stopped" afterward - but runProject() also writes _getState()'s (wrong)
return value straight to the root workflow component's own
cmp.wheel.json, so that component's own recorded state stays wrong
("finished"/"failed"/"unknown") even though the project overall
correctly shows "stopped".
Also update the existing #1020 test to call remove() (what stopProject()
actually calls) instead of a bare pause() - the two are not equivalent:
pause() alone is also used internally by _jumpHandler's "break" handling
(a normal, successful in-workflow loop exit, not a stop), and the
existing #_getState reproduction below documents that a break-terminated
loop's own state must still be "finished", not "stopped".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu
… stop (aicshud/WHEEL#1028) - _getState() now falls through to "stopped" (aicshud/WHEEL#1020's stoppedExternally flag) once hasUnknownComponent/hasFailedComponent are ruled out, instead of never reporting the documented "stopped" state at all. Checked last, not first: stopProject() is also used to abort the rest of a project when one of its own tasks fails (the "taskStateChanged" listener in handlers/projectController.js), and that path already records the failure via setStateFlag() before remove() runs - hasFailedComponent/hasUnknownComponent must keep taking priority over stoppedExternally there, matching the existing "failed must not be clobbered by stopped" contract (issue #1000). - Move where stoppedExternally is set from the generic "stop" handler (onStop, inside start()) to remove() itself, the one and only caller of which is stopProject(). pause() is also called directly by _jumpHandler's "break" handling as a normal, successful in-workflow loop exit - not a stop - and both emit the same "stop" event that onStop listens for, so leaving the flag in the generic handler would have made a break-terminated loop's own state incorrectly report "stopped" too. 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#1028:_getState()never returned"stopped", the documented state for an externally-stopped project (documentMD/user_guide/_reference/3_workflow_screen/1_graphview.md), even thoughstart()'sonStophandler setsstoppedExternally(aicshud/WHEEL#1020) specifically to record that case.Currently masked at the whole-project level because
onStopProject()unconditionally force-overwrites the project state to"stopped"afterward - butrunProject()also writes_getState()'s (wrong) return value straight to the root workflow component's owncmp.wheel.json, so that component's own recorded state stayed wrong ("finished"/"failed"/"unknown") even though the project overall correctly showed"stopped".What changed
_getState()now falls through to"stopped"oncehasUnknownComponent/hasFailedComponentare ruled out, instead of never reporting the documented"stopped"state at all. Checked last, not first:stopProject()is also used to abort the rest of a project when one of its own tasks fails (the"taskStateChanged"listener inhandlers/projectController.js), and that path already records the failure viasetStateFlag()beforeremove()runs -hasFailedComponent/hasUnknownComponentmust keep taking priority overstoppedExternallythere, matching the existing "failed must not be clobbered by stopped" contract (issue #1000).stoppedExternallyis set from the generic"stop"handler (onStop, insidestart()) toremove()itself, the one and only caller of which isstopProject().pause()is also called directly by_jumpHandler's "break" handling as a normal, successful in-workflow loop exit - not a stop - and both emit the same"stop"event thatonStoplistens for, so leaving the flag in the generic handler would have made a break-terminated loop's own state incorrectly report"stopped"too.Testing
#1020test to callremove()(whatstopProject()actually calls) instead of a barepause(), and added a new test confirming a barepause()(the "break" path) does NOT setstoppedExternally.npm run testDocker -w server— full suite green (1669 passing, 15 pending, 0 failing).Related
Filed against
aicshud/WHEEL#1028on GitLab (issue tracker for this project). Builds onaicshud/WHEEL#1020(#140, merged),#1023(#141, merged),#1027(#142, merged).🤖 Generated with Claude Code
https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu