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
10 changes: 7 additions & 3 deletions hermes_cli/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5644,21 +5644,25 @@ def _runner(
_cb: Callable[..., Any] = cb,
_key: tuple = callback_key,
_token: object = token,
_context: contextvars.Context = context,
_outcome: Dict[str, Any] = outcome,
_failure: Dict[str, Exception] = failure,
_done: threading.Event = done,
) -> None:
try:
# Route through _invoke_hook_callback so the
# additive-payload signature filtering (narrow
# legacy callbacks) applies on the worker too.
outcome["value"] = context.run(
_outcome["value"] = _context.run(
self._invoke_hook_callback, _cb, kwargs
)
except Exception as exc:
failure["exc"] = exc
_failure["exc"] = exc
finally:
with self._hook_timeout_lock:
if self._hook_running_callbacks.get(_key) is _token:
self._hook_running_callbacks.pop(_key, None)
done.set()
_done.set()

thread = threading.Thread(
target=_runner,
Expand Down
35 changes: 35 additions & 0 deletions tests/hermes_cli/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,41 @@ def fast(**_kwargs):
assert elapsed < 1.0, f"caller blocked for {elapsed:.2f}s after timeout"
hold.set()

def test_late_timed_out_hook_cannot_complete_successor(self, monkeypatch):
"""A late first worker cannot publish state for the next callback."""
monkeypatch.setattr(
"hermes_cli.plugins._resolve_hook_callback_timeout", lambda: 0.15
)

second_started = threading.Event()
release_second = threading.Event()
second_completed = threading.Event()

def first(**_kwargs):
assert second_started.wait(timeout=1.0)
return "late-first"

def second(**_kwargs):
second_started.set()
release_second.wait(timeout=10.0)
second_completed.set()
return "late-second"

def third(**_kwargs):
return "third"

mgr = PluginManager()
mgr._hooks["post_tool_call"] = [first, second, third]

try:
results = mgr.invoke_hook("post_tool_call")
assert not second_completed.is_set()
assert results == ["third"]
finally:
second_started.set()
release_second.set()
assert second_completed.wait(timeout=1.0)

def test_hook_callback_within_timeout_returns_value(self, monkeypatch):
monkeypatch.setattr(
"hermes_cli.plugins._resolve_hook_callback_timeout", lambda: 1.0
Expand Down
Loading