You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
approval(chat): a pending approval dies after 1h from a Redis TTL, and the restored one is unreachable from the approve path #13478
The session — including pending_approval — is not re-persisted while it sits awaiting a decision, so one hour after the last write it evaporates. _approve_command_internal then takes if not session: return {"status": "error", "error": "Session not found"}.
This is the real expiry. Not the 30-minute poll budget, and not a restart: pending_approval is persisted to Redis and get_session loads it back (session_manager.py:307, "CRITICAL: Restore pending approvals").
D2 — the restore path is unreachable from the approve path
_restore_pending_approval rebuilds the state from chat history and is explicitly documented as surviving restarts — but it runs only inside create_session, and the session it restores into gets a newsession_id. The persisted approval message and the GUI's approve button both carry the oldterminal_session_id, and POST /agent-terminal/sessions/{session_id}/approve is addressed by id. So even when the state is perfectly recoverable, the approve call cannot reach it.
Work
Refresh the session TTL while an approval is pending, or persist the pending approval under a key whose lifetime is not tied to session idleness
Make the approve path resolve a restored approval — either by keying approval on the conversation rather than the ephemeral terminal session id, or by having get_session fall back to the chat-history restore for a session id that carried a pending approval
changed the title [-]approval(chat): route the terminal-command gate through ApprovalGateService so a pending approval survives a restart[/-][+]approval(chat): a pending approval dies after 1h from a Redis TTL, and the restored one is unreachable from the approve path[/+]on Aug 4, 2026
$ git show origin/Dev_new_gui:autobot-backend/services/agent_terminal/session_manager.py \ | grep -cE 'APPROVAL_PENDING_SESSION_TTL|approval_conversation|_rebuild_session_from_pending_approval'7
Both defects, as described:
D1 — the 1-hour ceiling._persist_session now picks the TTL by what the session is doing: TTL_1_HOUR still collects genuinely idle sessions, while one holding a pending approval gets APPROVAL_PENDING_SESSION_TTL (7 days, AUTOBOT_APPROVAL_PENDING_SESSION_TTL_SECONDS). The thing being waited on is a person, and approval(chat): make expiry an explicit policy (default none) and stop reporting it as a command failure #13481 established that an approval does not expire on a timer — so this bounds the stored session, not the approval's validity.
D2 — the restore was unreachable from approve.agent_terminal:approval_conversation:{session_id} records which conversation a pending approval belongs to, and get_session follows it on a miss to rebuild the session under its original id — the one the approval message and the GUI button carry. Previously _restore_pending_approval ran only in create_session, which mints a new id, so a working restore could never be reached by the one path that needed it.
The rebuilt session takes AgentRole.CHAT_AGENT, the lowest privilege: the original role is not recorded in the restored approval, and a resurrected session exists only to let a human resolve an already-gated decision — it must never widen what the command may do.
Verification: 360 tests in services/agent_terminal/ + test_startup_imports.py. Both halves invert-tested, each failing on exactly its own regression; test_an_idle_session_still_expires_in_an_hour passes in both directions on purpose, guarding the GC this change could plausibly have broken.
Known limit, stated rather than implied: both the session record and the pointer live in Redis, so a full flush still loses a pending approval. That is data loss, a different failure from a TTL quietly expiring something never meant to be time-limited.
Activity
[-]approval(chat): route the terminal-command gate through ApprovalGateService so a pending approval survives a restart[/-][+]approval(chat): a pending approval dies after 1h from a Redis TTL, and the restored one is unreachable from the approve path[/+]fix(approval): stop a pending approval dying on the session idle TTL (#…
github-actions commentedon Aug 4, 2026
PR #13494 (merged to
Dev_new_gui) references this issue with a close keyword.If this issue is fully resolved, close it manually. If work remains, no action is needed.
mrveiss commentedon Aug 4, 2026
✅ Delivered — PR #13494, merged to
Dev_new_gui.Both defects, as described:
_persist_sessionnow picks the TTL by what the session is doing:TTL_1_HOURstill collects genuinely idle sessions, while one holding a pending approval getsAPPROVAL_PENDING_SESSION_TTL(7 days,AUTOBOT_APPROVAL_PENDING_SESSION_TTL_SECONDS). The thing being waited on is a person, and approval(chat): make expiry an explicit policy (default none) and stop reporting it as a command failure #13481 established that an approval does not expire on a timer — so this bounds the stored session, not the approval's validity.agent_terminal:approval_conversation:{session_id}records which conversation a pending approval belongs to, andget_sessionfollows it on a miss to rebuild the session under its original id — the one the approval message and the GUI button carry. Previously_restore_pending_approvalran only increate_session, which mints a new id, so a working restore could never be reached by the one path that needed it.The rebuilt session takes
AgentRole.CHAT_AGENT, the lowest privilege: the original role is not recorded in the restored approval, and a resurrected session exists only to let a human resolve an already-gated decision — it must never widen what the command may do.Verification: 360 tests in
services/agent_terminal/+test_startup_imports.py. Both halves invert-tested, each failing on exactly its own regression;test_an_idle_session_still_expires_in_an_hourpasses in both directions on purpose, guarding the GC this change could plausibly have broken.Known limit, stated rather than implied: both the session record and the pointer live in Redis, so a full flush still loses a pending approval. That is data loss, a different failure from a TTL quietly expiring something never meant to be time-limited.
Remaining #13216 children: #13479 and #13480.