Release: actionable ids for raw id-less pending approvals (#7510) (#7551)

* fix(approvals): mint actionable ids for raw id-less local pending entries

A raw id-less approval entry in _pending (agent-side _pending_result drops
{command, pattern_key, pattern_keys, description} verbatim when no gateway
notifier is registered for the session) is unactionable by contract: the
frontend owner-capture requires an approval_id, so the approval card can
neither be approved nor dismissed - the 1.5s poll re-serves it forever.

reconcile_gateway_pending_mirror_locked already mints gwlocal:<token> ids
for id-less _gateway_queues producers; extend the same contract to raw
non-mirror _pending entries with a stable gwlocal-mirrorless:<uuid> id
minted once onto the stored entry dict, so the id persists across polls
and frontend dismiss markers stick.

The exact-id respond path needs no further change: once the entry carries
an id, _resolve_approval_legacy pops it (also clearing the stale
stream-pointer 409 guard), stale explicit ids still fail closed (#527),
and the yolo Skip-all release drains it.

* docs(changelog): stamp #7510 (actionable ids for raw id-less pending approvals)

---------

Co-authored-by: CharlesMcquade <6466275+CharlesMcquade@users.noreply.github.com>
Co-authored-by: n <a@n>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-09-13 03:07:49 +00:00
1 parent e4d9b799e4
commit e2ce040f1f
3 files changed
+268

No files matched your search

+17
View File
@@ -265,6 +265,23 @@ def reconcile_gateway_pending_mirror_locked(session_key: str) -> tuple[dict | No
live_local_tokens.add(live_entry_token)
if not str(live_data.get("approval_id") or "").strip():
live_data["approval_id"] = f"gwlocal:{live_entry_token}"
# A raw id-less LOCAL entry in `_pending` (agent-side _pending_result drops
# `{command, pattern_key, pattern_keys, description}` verbatim when no
# gateway notifier is registered) is unactionable by contract: the frontend
# owner-capture requires an approval_id, so its card can neither be
# approved nor dismissed — the poll re-serves it forever. Mint a stable id
# on the entry itself (the same contract the producer-tokenization loop
# above applies to _gateway_queues producers). Minting on the stored dict
# keeps the id stable across polls, so frontend dismiss markers persist.
for entry in queue_list:
if not isinstance(entry, dict) or _is_gateway_mirror_entry(entry):
continue
if str(entry.get("approval_id") or "").strip():
continue
if str(entry.get("run_id") or "").strip():
continue
entry["approval_id"] = f"gwlocal-mirrorless:{uuid.uuid4().hex}"
live_token = (
_gateway_mirror_entry_token(live_head_entry)
if live_head_entry and live_head_data