fix: parked approval escalations that leave chats permanently stuck - #39
Open
shipdocs wants to merge 3 commits into
Open
fix: parked approval escalations that leave chats permanently stuck#39shipdocs wants to merge 3 commits into
shipdocs wants to merge 3 commits into
Conversation
shipdocs
marked this pull request as ready for review
August 14, 2026 12:18
Approval cards could go missing without a trace: EventBus.publish() awaits escalation_created listeners via asyncio.gather(return_exceptions=True), so any exception inside _mirror_escalation was silently swallowed — no log, no card in any channel, and no pending-escalation future, leaving the parked task with no way to be answered. _mirror_escalation now degrades to the activity channel and logs loudly instead of losing the card. As a safety net for cards that still fail to render, an unambiguous decision word typed in chat (now including "proceed") can resume a parked approval checkpoint directly, without requiring a card click. Also routes the checkpoint-resume progress note through the WorkItem-owned metadata path instead of writing task.metadata directly, which was tripping the metadata-ownership invariant checker on every resume of a work-item-linked task. Separately, the channel outbound-dispatch loop is the sole consumer for every channel; a single send that never returns used to stall delivery for all channels until restart. Outbound sends are now bounded by a configurable dispatch_timeout_seconds.
…on, team chat Sequences the fixes into phases: proactive orphan-checkpoint sweep, giving the Secretary a real delegate-to-team action, and a multi-party team chat built on the existing activity channel and AgentMessage fan-out.
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
Chats that hit a tool-approval escalation could get permanently stuck with no way to unblock them — restarting the engine just re-parks on the same escalation forever. Two commits, addressing the same failure class from different angles:
1.
fix(ui): keep approval escalations actionablesed -nexpressions that use regex addresses, avoiding unnecessary approval escalation.2.
fix: recover parked approval escalations and outbound dispatch hangsRoot-caused via a live
.opc/ui_state.dbinspection of a stuck run: for the incident under investigation, zerohuman_escalationcard messages existed anywhere (session or activity channel) even though the task had correctly parked.EventBus.publish()awaits everyescalation_createdlistener throughasyncio.gather(..., return_exceptions=True). Any exception inside_mirror_escalationwas silently swallowed — no log, no card in any channel, and critically no pending-escalationfutureever got registered, so even a later correct reply had nothing live to resolve._mirror_escalationnow catches failures at each resolution step, logs them, and degrades to the project activity channel instead of losing the card outright.task.metadata, butprogress_logis WorkItem-owned metadata — this tripped the metadata-ownership invariant checker (metadata_ownership_conflict/violationwarnings) on every resume of a work-item-linked task. Now routed through the WorkItem-owned path (append_work_item_progress) when the task is linked to a work item.dispatch_timeout_seconds(default 30s).User impact
Read-only inspection commands no longer request approval unnecessarily, users can send a recovery or follow-up message while work is active, pending approvals remain actionable from the chat where the user is working, a card failing to render can no longer strand a task forever, and one hung channel send can no longer stall message delivery for every other channel.
Test plan
tests/test_shell_safety.py,tests/test_ws_handler_escalations.py,tests/test_approval_escalation_reply.py,tests/test_approval_engine.py,tests/test_chat_store_progress_folding.py,tests/test_metadata_ownership.py— all greentests/test_attempt_ledger.py,tests/test_checkpoint_answer_live_dispatcher.py,tests/test_company_recruiter.py,tests/test_company_runtime_suspend_resume.py,tests/test_request_user_input_collab.py,tests/test_run_failure_settlement.py,tests/test_runtime_config_enforcement.py,tests/test_runtime_v2_migration.py,tests/test_self_evolution_pipeline.py,tests/test_stale_checkpoint_selfheal.py— 199 passedtests/test_session_integration.py(192 passed) — new regression: escalation card survives a routing-resolution exceptiontest_stale_checkpoint_selfheal.py), "proceed"/"go ahead"/"continue" normalize to an approval decision (test_approval_escalation_reply.py)npm run test:contracts(9 passed),npm run typecheck,npm run buildwith Node.js 22