Skip to content

approval(chat): a pending approval dies after 1h from a Redis TTL, and the restored one is unreachable from the approve path #13478

Closed

Description

@mrveiss

Child 1 of #13216. Rewritten — the original premise ("a restart loses the approval") was wrong; see the correction comment on #13216.

D1 — a hard one-hour ceiling from a Redis TTL

# services/agent_terminal/session_manager.py:388
await self.redis_client.setex(key, TTL_1_HOUR, json_data)   # TTL_1_HOUR = 3_600

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 new session_id. The persisted approval message and the GUI's approve button both carry the old terminal_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
  • Decide deliberately whether an approval should expire at all (see approval(chat): make expiry an explicit policy (default none) and stop reporting it as a command failure #13481); if it should not, the TTL must not be what decides it

Verification

Approve a command >1 hour after the prompt was raised; it executes rather than returning Session not found.

Refs #13216

Activity

  1. 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
  2. github-actions commented on Aug 4, 2026

    @github-actions
    Contributor

    PR #13494 (merged to Dev_new_gui) references this issue with a close keyword.

    fix(approval): stop a pending approval dying on the session idle TTL (#13478)

    If this issue is fully resolved, close it manually. If work remains, no action is needed.

  3. mrveiss commented on Aug 4, 2026

    @mrveiss
    OwnerAuthor

    ✅ Delivered — PR #13494, merged to Dev_new_gui.

    $ 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.

    Remaining #13216 children: #13479 and #13480.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions