Skip to content

[Bug]: The on_session_finalize hook is not being fired when gateway sessions expire due to configured idle time. #14981

@UmbraAtrox

Description

@UmbraAtrox

Bug Description

Summary
The background session expiry watcher in gateway/run.py cleans up expired sessions but does not fire the on_session_finalize hook, despite the documentation stating this hook should fire when "CLI/gateway tears down an active session" hooks.md:613-634 .

Detailed Analysis
Expected Behavior
The on_session_finalize hook is documented to fire when:

CLI exits with an active agent
Gateway tears down an active session (including GC/cleanup scenarios)
User runs /new or /reset commands
Actual Behavior in Idle Timeout
When a session expires due to idle time, the _session_expiry_watcher function in gateway/run.py run.py:2247-2394 performs these actions:

Flushes memories via _async_flush_memories
Cleans up agent resources via _cleanup_agent_resources
Evicts the agent from cache via _evict_cached_agent
Marks the session as flushed
However, it does not call the on_session_finalize hook.

Evidence of the Bug
The expiry watcher code has no invocation of hermes_cli.plugins.invoke_hook for on_session_finalize
Tests in tests/gateway/test_session_boundary_hooks.py verify the hook fires for /new commands and shutdown test_session_boundary_hooks.py:76-156 , but there are no tests for idle timeout scenarios
The CLI implementation properly fires the hook via _notify_session_boundary cli.py:4505-4533
Root Cause
The session expiry watcher was designed to proactively flush memories before the next user message arrives, but the hook firing mechanism was overlooked. This is inconsistent with the documented behavior and with how other session teardown scenarios handle the hook.

Steps to Reproduce

Write plugin with on_session_finalize

LOG_FILE=/tmp/plugin.log
def on_session_finalize(session_id=None, **kwargs):
    entry = {
        "timestamp": datetime.now().isoformat(),
        "session_id": session_id,
        "kwargs": kwargs,
    }
    with open(LOG_FILE, "a") as f:
        f.write(json.dumps(entry) + "\n")
        f.flush()
def register(ctx):
    ctx.register_hook("on_session_finalize", on_session_finalize)
    with open(LOG_FILE, "a") as f:
        f.write(json.dumps({"register": "on_session_finalize registered"}) + "\n")
        f.flush()

Open/close cli session -> notice new entry in plugin.log
Start gateway session
Wait for idle timeout ->notice no new log entry

Expected Behavior

Should have fired

Actual Behavior

didn't

Affected Component

Other

Messaging Platform (if gateway-related)

No response

Debug Report

no logs as nothing happened

Operating System

debian 13

Python Version

No response

Hermes Version

No response

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

No response

Proposed Fix (optional)

No response

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/gatewayGateway runner, session dispatch, deliverycomp/pluginsPlugin system and bundled pluginstype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions