Fix:incorrectly removes action event - #4616
Conversation
|
🚦 CI is currently failing on this PR's latest commit. Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request This is an automated check - no AI was used to generate this comment. |
|
✅ Review complete. This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here. |
all-hands-bot
left a comment
There was a problem hiding this comment.
🟡 Acceptable — Correct, targeted bug fix for a real production issue. The core logic is sound.
What the fix does: On cold resume from persistence in WAITING_FOR_CONFIRMATION state, rebuild_view() triggers View.from_events() → enforce_properties() → ToolCallMatchingProperty.enforce(), which was stripping the pending ActionEvent because no matching ObservationEvent existed yet. The result was a malformed [system, user, tool] history (missing the assistant(tool_calls) entry) that providers reject. The fix introduces allowed_unmatched_action_ids to thread the set of legitimately-unmatched action IDs through the property enforcement pipeline so they are preserved.
The regression test is well-structured: it exercises a real code path end-to-end, uses mocking only at the correct I/O boundary (the LiteLLM call), and asserts on the actual message sequence shape rather than internal call counts. PR description evidence is solid.
One minor design note raised inline — not blocking.
[RISK ASSESSMENT]
- [Overall PR]
⚠️ Risk Assessment: 🟢 LOW
Narrowly scoped bug fix in the persistence resume path. Fully backward-compatible API change (keyword-only parameter,Nonedefault). Covered by a purpose-built regression test. No dependency changes. No public API surface changes.
VERDICT:
✅ Worth merging: The fix is correct, the test proves it, and the risk is low.
KEY INSIGHT:
ToolCallMatchingProperty.enforce() correctly cleans up orphaned events in general, but must be told which action IDs are legitimately orphaned (awaiting confirmation) to avoid corrupting the resumed conversation history.
Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:
- Add a
.agents/skills/custom-codereview-guide.mdfile to your branch (or edit it if one already exists) with the/codereviewtrigger and the context the reviewer is missing. See the customization docs for the required frontmatter format.- Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
- When your PR is merged, the guideline file goes through normal code review by repository maintainers.
Resolve with AI? Install the iterate skill in your agent and run
/iterateto automatically drive this PR through CI, review, and QA until it's merge-ready.Was this review helpful? React with 👍 or 👎 to give feedback.
This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation. View conversation
| allowed_unmatched_action_ids = allowed_unmatched_action_ids or set() | ||
| for property in ALL_PROPERTIES: | ||
| events_to_forget = property.enforce(self.events, all_events) | ||
| if isinstance(property, ToolCallMatchingProperty): |
There was a problem hiding this comment.
🟡 Suggestion: The isinstance(property, ToolCallMatchingProperty) check breaks the abstraction of the polymorphic property loop — all other properties are treated uniformly, but this one concrete type is singled out after-the-fact.
A cleaner design would pass allowed_unmatched_action_ids into enforce() itself and let ToolCallMatchingProperty handle it internally (other properties would simply ignore the extra argument). That said, changing ViewPropertyBase.enforce()'s signature is a larger refactor that goes beyond a focused bug fix. Acceptable as-is; worth tracking as a follow-up cleanup.
|
🔍 Review in progress… We are performing the review through OpenHands Cloud Automation. You can log in and view the conversation here. |
5 similar comments
|
🔍 Review in progress… We are performing the review through OpenHands Cloud Automation. You can log in and view the conversation here. |
|
🔍 Review in progress… We are performing the review through OpenHands Cloud Automation. You can log in and view the conversation here. |
|
🔍 Review in progress… We are performing the review through OpenHands Cloud Automation. You can log in and view the conversation here. |
|
🔍 Review in progress… We are performing the review through OpenHands Cloud Automation. You can log in and view the conversation here. |
|
🔍 Review in progress… We are performing the review through OpenHands Cloud Automation. You can log in and view the conversation here. |
|
✅ Review complete. This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here. |
all-hands-bot
left a comment
There was a problem hiding this comment.
🟡 Acceptable — The code fix is targeted and sound, and the focused regression test passes. I found no additional code defect requiring an inline comment; the existing inline design note on View.enforce_properties is not duplicated here.
The persisted confirmation-resume path now preserves a pending action only while the conversation is in WAITING_FOR_CONFIRMATION, then resumes the normal approval flow. The implementation is narrow, backward-compatible through keyword-only optional parameters, and the test exercises the real local conversation/persistence path while stubbing only the LiteLLM boundary.
[CRITICAL ISSUES]
- [PR description] Merge gate: The latest commit has a failing required
Validate PR descriptioncheck. GitHub reports that linked issue #4532 has neitherready-for-devnor a pre-rollout creation date, somergeable_stateisblocked. The issue is not a code regression, but it must be resolved or the repository policy must be satisfied before merge.
[TESTING GAPS]
- None found. The focused regression test passed locally:
1 passed, 10 deselected. The PR description also includes the exact command and resulting output, plus the provider-request shape change.
[RISK ASSESSMENT]
- [Overall PR]
⚠️ Risk Assessment: 🟢 LOW
The implementation is a narrow persistence/view fix with no dependency changes or public API removal. The remaining risk is operational: the required PR-description check currently prevents merge despite the code being acceptable.
VERDICT:
❌ Needs rework: The core code is ready, but the current required CI/description gate is failing; do not merge until issue #4532 satisfies the repository readiness policy or the gate is intentionally waived.
KEY INSIGHT:
Legitimate pending confirmation actions are a stateful exception to the normal tool-call/observation pairing invariant, so the exception must be attached to the persisted conversation state rather than weakening the general view property.
Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:
- Add a
.agents/skills/custom-codereview-guide.mdfile to your branch (or edit it if one already exists) with the/codereviewtrigger and the context the reviewer is missing (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.- Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
- When your PR is merged, the guideline file goes through normal code review by repository maintainers.
Resolve with AI? Install the iterate skill in your agent and run
/iterateto automatically drive this PR through CI, review, and QA until it is merge-ready.Was this review helpful? React with 👍 or 👎 to give feedback.
_This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation. View conversation
HUMAN:
I verified the persisted confirmation resume flow locally and this PR is ready for review.
AGENT:
This PR fixes and verifies the persisted confirmation approval flow end-to-end in the local SDK path. I reproduced the relevant lifecycle with a real
Conversation: send a user message, pause onAlwaysConfirm, close the conversation, reopen it from the sameconversation_idandpersistence_dir, approve by callingrun(), then inspect the second LLM request shape.Why
When a conversation pauses for confirmation, then gets closed and resumed from persistence before approval, the follow-up LLM request could include a
toolresult without the preceding assistanttool_callsmessage. Providers reject that malformed history, even though the persisted event log itself contains the matchingActionEventandObservationEvent.This breaks the normal web app flow where approval happens in a later request/process.
Summary
assistant(tool_calls)before the matchingtoolresult.Issue Number
Fixes #4532
How to Test
I ran the focused regression test:
Result:
I also ran pre-commit on the changed test file:
Result:
The new regression test exercises the original issue shape without provider credentials by capturing the actual messages passed to the LiteLLM boundary. The second LLM request now contains:
instead of the broken persisted-resume shape:
Video/Screenshots
N/A. This is a backend SDK persistence/context-view bug with no UI surface.
Design Doc
N/A. The fix is narrowly scoped to conversation view rebuilding for confirmation-paused state.
Type
Notes
No rollout concerns.