fix(bot-mode): a pending command approval is not a failed DM delivery
terminal_tool's approval gate answers `status: pending_approval` with an EMPTY `error` (#28323) and no `session_id`, so _spawn_delivery's specific branch (`if parsed.get("error")`) was skipped and every unanswered approval fell through to "Delivery to X failed to start: no process id returned" — blaming the spawn for an approval nobody in a non-interactive turn (api_server, `hermes peer dm`, cron) could grant. - _spawn_delivery: the pending shape gets its own message (the runner command needs terminal approval nobody in this turn can grant); a local/peer DM adds "nothing was sent — approve it or add it to command_allowlist and send again". Ownership is never transferred, so the existing finally still reclaims the plaintext DM file. - _try_relay_delivery: the envelope is queued on disk BEFORE the reply waiter spawns and the Desktop drains it independently, so ANY waiter spawn failure is a lost wake-up, not a failed delivery; reporting it as an error made the sender resend and deliver the message twice. The relay path now returns the shape _start_delivery's live-owner branch already uses (status queued + notification_error + "Do NOT resend") instead of inventing a new status value nothing reads. Slimmer redo of #92971 by @jonpol01 (same diagnosis, same relay/local split on `dm_file is None`); the source-text contract test and the `sent_no_reply_wake` status were dropped. Fixes #111716 Co-authored-by: John Paul Soliva <soliva.johnpaul@icloud.com>
This commit is contained in:
+19
-1
@@ -289,7 +289,19 @@ def _try_relay_delivery(root: Path, raw_target: str, content: str, me: str, *,
|
||||
# per the #93091 reason enum).
|
||||
return json.dumps({"error": str(exc), "reason": exc.reason})
|
||||
label = f"@{match['handle']} on {match['connection_label'] or match['connection_id']}"
|
||||
return _spawn_delivery(waiter_command(root, envelope), label, task_id=task_id, agent=agent)
|
||||
raw = _spawn_delivery(waiter_command(root, envelope), label, task_id=task_id, agent=agent)
|
||||
waiter_error = json.loads(raw).get("error")
|
||||
if not waiter_error:
|
||||
return raw
|
||||
# The envelope is already queued and the Desktop drains it on its own, so a waiter that
|
||||
# failed to start loses only the reply wake-up. Reporting a hard failure here makes the
|
||||
# sender resend and deliver the message twice. Same shape as the live-owner branch of
|
||||
# _start_delivery: queued + notification_error.
|
||||
return json.dumps({
|
||||
"status": "queued", "to": label, "notification_error": waiter_error,
|
||||
"detail": (f"Message queued for {label}; the relay delivers it on its own, but the reply "
|
||||
"waiter did not start, so the reply will NOT wake you. Do NOT resend."),
|
||||
})
|
||||
except Exception:
|
||||
logger.debug("relay delivery attempt failed", exc_info=True)
|
||||
return None
|
||||
@@ -602,6 +614,12 @@ def _spawn_delivery(command: str, label: str, *, dm_file: Optional[str] = None,
|
||||
proc_id = parsed.get("session_id") or ""
|
||||
if parsed.get("error"):
|
||||
return _err(f"Delivery to {label} failed to start: {parsed['error']}")
|
||||
if parsed.get("status") == "pending_approval":
|
||||
# terminal_tool's approval gate answers with an EMPTY error and no session_id: the runner
|
||||
# never launched because nobody in this turn could approve its command.
|
||||
return _err(f"Delivery to {label} failed to start: its command needs terminal approval that nobody "
|
||||
"in this turn can grant" + (", so nothing was sent. Approve it (or add it to "
|
||||
"command_allowlist) and send again." if dm_file else "."))
|
||||
if not proc_id:
|
||||
return _err(f"Delivery to {label} failed to start: no process id returned")
|
||||
# From here the background runner owns the file (removed after the consumer finishes).
|
||||
|
||||
Reference in New Issue
Block a user