fix: queue simultaneous approval requests per session (fixes #527) - #546
1 commit merged into
Conversation
Changes _pending from a single overwriting dict value to a list, so parallel tool calls each get their own approval slot. api/routes.py: - Wraps submit_pending() to append to a list and assign a stable approval_id (uuid4) to each entry. - _handle_approval_pending() returns the first queued entry plus pending_count so the UI can show '1 of N'. - _handle_approval_respond() pops by approval_id (falls back to oldest entry for backward-compat with old clients). - Backward-compat: legacy single-dict values in _pending are handled without crashing. static/messages.js: - respondApproval() sends approval_id in the POST body. - showApprovalCard() accepts pendingCount, shows '1 of N pending' counter when multiple approvals are queued. - _approvalCurrentId tracks the approval_id of the displayed card. - Poll loop passes pending_count to showApprovalCard. static/index.html: - Adds approvalCounter element for the '1 of N' display. tests/test_approval_queue.py: - 14 tests: static-analysis checks (Python + JS + HTML), functional tests that inject two simultaneous approvals and verify both are surfaced and independently resolvable.
Independent Review: PR #546 — approval queue multi-entry supportSecurity AuditClean. The Code ReviewProblem: Fix: Three-layer solution that's clean and backward-compatible:
Frontend changes (messages.js):
One design note: The current UI shows only the first queued approval and reveals the next one after the user responds. This is the right UX — showing all N simultaneously would overwhelm users. The "1 of N pending" counter gives enough context. One thing to verify (can't test without a browser): After responding to approval 1 of N, does the next approval card appear immediately? The poll interval is 1.5s, so there could be a brief gap. If the SSE Tests14 new tests — all pass. Good mix:
Full suite: 1244 passed, 0 regressions, 58 skipped. The 2 VerdictApproved. Clean, well-tested fix for a real concurrency bug. Backward-compatible with both old agent versions (legacy dict) and old frontend clients (no approval_id). Ready to merge. |
BUG-1 (CRITICAL): messages.js line 522 — mismatched quote in
setComposerStatus('Reconnecting…') caused JS syntax error on the
reconnect path.
BUG-2 (HIGH): messages.js line 491 — broken template literal
'\\n\\n*{d.hint}*' restored to '\n\n*${d.hint}*'. Error hint
text was non-functional (missing $ prefix and escaped newlines).
BUG-3 (HIGH): messages.js — showApprovalCard(pending, pendingCount),
_approvalCurrentId, and approval_id in respondApproval() were removed,
regressing the simultaneous approval queue fix from PR #546. Restored
all three, including the '1 of N pending' counter and poll passthrough.
BUG-4 (LOW): api/streaming.py — MiniMax thinking delimiter regex
missing closing pipe: <|channel> -> <|channel|> in both
_strip_thinking_markup() and _looks_invalid_generated_title().
ALSO: test_issue487b.py docstring changed to raw string to fix
DeprecationWarning for invalid escape sequence '\s'.
BUG-1 (CRITICAL): messages.js line 522 — mismatched quote in
setComposerStatus('Reconnecting…') caused JS syntax error on the
reconnect path.
BUG-2 (HIGH): messages.js line 491 — broken template literal
'\\n\\n*{d.hint}*' restored to '\n\n*${d.hint}*'. Error hint
text was non-functional (missing $ prefix and escaped newlines).
BUG-3 (HIGH): messages.js — showApprovalCard(pending, pendingCount),
_approvalCurrentId, and approval_id in respondApproval() were removed,
regressing the simultaneous approval queue fix from PR nesquena#546. Restored
all three, including the '1 of N pending' counter and poll passthrough.
BUG-4 (LOW): api/streaming.py — MiniMax thinking delimiter regex
missing closing pipe: <|channel> -> <|channel|> in both
_strip_thinking_markup() and _looks_invalid_generated_title().
ALSO: test_issue487b.py docstring changed to raw string to fix
DeprecationWarning for invalid escape sequence '\s'.
BUG-1 (CRITICAL): messages.js line 522 — mismatched quote in
setComposerStatus('Reconnecting…') caused JS syntax error on the
reconnect path.
BUG-2 (HIGH): messages.js line 491 — broken template literal
'\\n\\n*{d.hint}*' restored to '\n\n*${d.hint}*'. Error hint
text was non-functional (missing $ prefix and escaped newlines).
BUG-3 (HIGH): messages.js — showApprovalCard(pending, pendingCount),
_approvalCurrentId, and approval_id in respondApproval() were removed,
regressing the simultaneous approval queue fix from PR nesquena#546. Restored
all three, including the '1 of N pending' counter and poll passthrough.
BUG-4 (LOW): api/streaming.py — MiniMax thinking delimiter regex
missing closing pipe: <|channel> -> <|channel|> in both
_strip_thinking_markup() and _looks_invalid_generated_title().
ALSO: test_issue487b.py docstring changed to raw string to fix
DeprecationWarning for invalid escape sequence '\s'.
Summary
Fixes #527 — simultaneous approval requests overwrote each other.
When an agentic session spawned parallel tool calls that each needed approval, only the last one was ever visible in the UI. Earlier approvals were silently dropped.
Changes
api/routes.pysubmit_pending()wrapper appends to a list and assigns a stableapproval_id(uuid4) to each entry — instead of overwriting_pending[sid]with a single dict_handle_approval_pending()returns{pending: <first entry>, pending_count: N}so the UI can show "1 of N"_handle_approval_respond()pops byapproval_id(falls back to oldest entry for backward-compat with clients that don't send it)static/messages.jsrespondApproval()sendsapproval_idin the POST bodyshowApprovalCard(pending, pendingCount)shows "1 of N pending" counter when multiple are queued_approvalCurrentIdtracks which approval is displayed so respond targets the right onepending_countthrough toshowApprovalCardstatic/index.html<div id="approvalCounter">inside the approval card for the "1 of N" displaytests/test_approval_queue.pyTest results
1302/1302 passing (+14 new). No regressions.
Closes #527