Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions gateway/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -3515,6 +3515,22 @@ def _finalize_shutdown_agents(self, active_agents: Dict[str, Any]) -> None:
pass
self._cleanup_agent_resources(agent)

# Clear session-level state so gateway restart starts fresh:
# model/reasoning overrides, pending approvals, and YOLO state
# must not survive a shutdown boundary.
self._session_model_overrides.clear()
if hasattr(self, "_session_reasoning_overrides"):
self._session_reasoning_overrides.clear()
if hasattr(self, "_pending_approvals"):
self._pending_approvals.clear()
if hasattr(self, "_pending_model_notes"):
self._pending_model_notes.clear()
try:
from tools.approval import clear_all_sessions
clear_all_sessions()
except Exception:
pass

def _cleanup_agent_resources(self, agent: Any) -> None:
"""Best-effort cleanup for temporary or cached agent instances."""
if agent is None:
Expand Down Expand Up @@ -8115,6 +8131,34 @@ async def _handle_message_with_agent(self, event, source, _quick_key: str, run_g
source.chat_id or "unknown", _msg_preview,
)

# ── Auth-failure auto-restart ────────────────────────────────
# When _try_refresh_nous_client_credentials exhausts every
# credential path (Portal OAuth, refresh token, shared store),
# the agent writes ~/.hermes/.auth_restart_requested. Check
# here at the start of each message and auto-restart if the
# marker is present and recent. This is the "last resort" step:
# if session clearing and shared creds don't help, restart the
# gateway process to start with a clean credential state.
_restart_marker = _hermes_home / ".auth_restart_requested"
if _restart_marker.exists():
try:
_data = json.loads(_restart_marker.read_text())
_ts = _data.get("timestamp", 0)
if time.time() - _ts < 300: # < 5 minutes
logger.warning(
"Auth-restart marker found (.auth_restart_requested) for session %s "
"— triggering gateway restart as last resort.",
session_key,
)
_restart_marker.unlink(missing_ok=True)
self.request_restart(detached=True, via_service=False)
return ""
except Exception:
try:
_restart_marker.unlink(missing_ok=True)
except Exception:
pass

# Get or create session
# Topic-mode DMs: rewrite a stale/foreign thread_id to the user's
# last-active topic so a cross-topic Reply or stripped plain reply
Expand Down
18 changes: 18 additions & 0 deletions tools/approval.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,24 @@ def clear_session(session_key: str) -> None:
entry.event.set()


def clear_all_sessions() -> None:
"""Remove ALL approval and YOLO state across every session.

Called during gateway finalize-shutdown so a restart starts fresh —
model/reasoning overrides, per-session approvals, and YOLO state
from the previous gateway lifecycle must not carry over.
"""
with _lock:
_session_approved.clear()
_session_yolo.clear()
for entries in _gateway_queues.values():
for entry in entries:
entry.result = "deny"
entry.event.set()
_gateway_queues.clear()
_pending.clear()


def is_session_yolo_enabled(session_key: str) -> bool:
"""Return True when YOLO bypass is enabled for a specific session."""
if not session_key:
Expand Down
Loading