Fixes conversations that stop responding due to stale execution status#2466
Fixes conversations that stop responding due to stale execution status#2466DoubleDensity wants to merge 2 commits intoOpenHands:mainfrom
Conversation
|
@OpenHands /codereview-roasted |
|
I'm on it! xingyaoww can track my progress at all-hands.dev |
xingyaoww
left a comment
There was a problem hiding this comment.
🔴 Needs improvement — Violates fundamental principles
Taste Rating: 🔴
This change is well-intentioned but architecturally wrong. It solves a problem that was already solved by PR #2384, and in the process introduces a worse version of the fix that breaks state semantics.
[CRITICAL ISSUES]
[state.py, Lines 339–346] Data Structure / State Ownership: Unconditional terminal state reset on resume destroys meaningful state
The core problem: this resets FINISHED, ERROR, and STUCK to IDLE every time a conversation is loaded, regardless of whether the user actually wants to continue it.
-
FINISHED → IDLE on resume is wrong. FINISHED means "the agent completed its task." That's meaningful, persistent information. If a user resumes a conversation just to inspect it (read history, check results), you've silently nuked the completion state. The existing fix in
local_conversation.py(from PR #2384) already handles FINISHED → IDLE correctly — it triggers only when a new user message arrives, which is an explicit signal to continue. This is the right semantic. -
ERROR → IDLE on resume is dangerous. ERROR indicates something broke. Silently resetting it to IDLE on load masks the failure. PR #2384 already handles this properly —
run()allows ERROR → RUNNING, letting the caller decide to retry. Don't make that decision for them in the state constructor. -
STUCK → IDLE on resume is redundant. PR #2384 already added STUCK → IDLE on
send_message()and STUCK → RUNNING onrun(). Both transitions require an explicit user action. This is correct. Resetting at load time just adds a redundant layer.
[state.py] Breaking Change: Side-effect on load breaks read-only resume
Because _autosave_enabled is set to True on the very next line, the subsequent state.agent = agent assignment will trigger __setattr__ → autosave, persisting the IDLE status. This means merely loading a finished conversation changes its persisted state. That's a violation of the principle that reading should not have side effects.
[IMPROVEMENT OPPORTUNITIES]
Redundant defense-in-depth that creates confusion:
We now have terminal state resets in THREE places:
state.py::create()— this PR (on resume)local_conversation.py::send_message()— from PR #2384 (on new message)local_conversation.py::run()— from PR #2384 (on explicit run)
Places (2) and (3) are correct — they trigger on explicit user actions. Place (1) is implicit and unconditional. Having both creates ambiguity about which layer owns state transitions. The answer should be clear: local_conversation.py owns execution state transitions, state.py owns persistence. Don't mix them.
[TESTING GAPS]
No tests for the actual change. The PR adds 10 lines to state.py but zero test coverage for the new behavior. Specifically missing:
- Test that FINISHED state survives resume when no new message is sent
- Test that ERROR state is preserved on resume for inspection
- Test that the state.py reset interacts correctly with the
local_conversation.pyresets
[PR DESCRIPTION]
No Evidence section. The PR links to a Slack thread and an OpenHands conversation, but doesn't demonstrate the actual fix working. Show a before/after of a stuck conversation being recovered.
VERDICT:
❌ Needs rework. This change is unnecessary — PR #2384 already solved the stuck conversation problem at the right layer (explicit user actions in local_conversation.py). Adding an unconditional reset at the state loading layer breaks FINISHED semantics, creates redundant transition logic, and has no tests. The fix should be removed entirely.
KEY INSIGHT:
The right place to reset terminal states is in response to explicit user actions (send_message, run), not during state deserialization. PR #2384 already got this right.
SummaryI performed a Linus Torvalds-style roasted code review ( Review PostedThe review was posted as a COMMENT on the PR at: Key FindingsTaste Rating: 🔴 Needs improvement The PR adds a 10-line change to
Verdict: ❌ Needs rework — recommended removing the change entirely since PR #2384 already solved the problem at the correct architectural layer. |
|
after additional testing I can confirm that this does not resolve the issue -- digging deeper for a better approach |
https://openhands-ai.slack.com/archives/C06U8UTKSAD/p1773351234338059?thread_ts=1773161499.244319&cid=C06U8UTKSAD
Fix was generated by OpenHands here:
https://app.all-hands.dev/conversations/9eaa41581dfa476aaca0009118cfd5be
Summary
This builds on the fix from @xingyaoww to resolve the issue of conversations that stop responding and requiring base_state.json to be cleared by directly clearing blocked conversation states
#2384
Checklist