mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-13 03:52:00 +00:00
fix(cli): pin HERMES_KANBAN_BOARD at chat boot to stop subprocess board drift
Without an explicit pin, in-process kanban tools and shelled-out `hermes kanban …` subprocesses resolve the active board on different paths: the env var when set, otherwise the global `<root>/kanban/current` file. When a concurrent session toggles the current-board pointer mid-turn, the same chat ends up routing tool calls to board A while its shell calls hit board B, surfacing as phantom "no such task" errors. Pin the resolved board into env once at `cmd_chat` boot when HERMES_KANBAN_BOARD isn't already set. Mirrors what the dispatcher does for spawned workers (kanban_db.py:2622-2623). Idempotent and a no-op when the env is already pinned by the caller. Closes #20074
This commit is contained in:
parent
d472d697cd
commit
b22b3f506a
2 changed files with 76 additions and 0 deletions
|
|
@ -1216,6 +1216,26 @@ def _launch_tui(
|
||||||
sys.exit(code)
|
sys.exit(code)
|
||||||
|
|
||||||
|
|
||||||
|
def _pin_kanban_board_env() -> None:
|
||||||
|
"""Pin the active kanban board into ``HERMES_KANBAN_BOARD`` for the chat session.
|
||||||
|
|
||||||
|
Without this, in-process tools (``kanban_*``) and shelled-out CLI calls
|
||||||
|
(``hermes kanban …``) resolve the board on different paths: the env-pin if
|
||||||
|
set, otherwise the global ``<root>/kanban/current`` file. A concurrent
|
||||||
|
``hermes kanban boards switch`` from another session can flip the file
|
||||||
|
mid-turn, so the same chat sees its tool calls hit board A while its shell
|
||||||
|
calls hit board B (#20074). Pinning at chat boot mirrors what the
|
||||||
|
dispatcher already does for spawned workers.
|
||||||
|
"""
|
||||||
|
if os.environ.get("HERMES_KANBAN_BOARD"):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
from hermes_cli.kanban_db import get_current_board
|
||||||
|
os.environ["HERMES_KANBAN_BOARD"] = get_current_board()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def cmd_chat(args):
|
def cmd_chat(args):
|
||||||
"""Run interactive chat CLI."""
|
"""Run interactive chat CLI."""
|
||||||
use_tui = getattr(args, "tui", False) or os.environ.get("HERMES_TUI") == "1"
|
use_tui = getattr(args, "tui", False) or os.environ.get("HERMES_TUI") == "1"
|
||||||
|
|
@ -1324,6 +1344,8 @@ def cmd_chat(args):
|
||||||
if getattr(args, "source", None):
|
if getattr(args, "source", None):
|
||||||
os.environ["HERMES_SESSION_SOURCE"] = args.source
|
os.environ["HERMES_SESSION_SOURCE"] = args.source
|
||||||
|
|
||||||
|
_pin_kanban_board_env()
|
||||||
|
|
||||||
if use_tui:
|
if use_tui:
|
||||||
_launch_tui(
|
_launch_tui(
|
||||||
getattr(args, "resume", None),
|
getattr(args, "resume", None),
|
||||||
|
|
|
||||||
54
tests/hermes_cli/test_pin_kanban_board_env.py
Normal file
54
tests/hermes_cli/test_pin_kanban_board_env.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
"""Tests for `_pin_kanban_board_env` helper invoked by `cmd_chat`.
|
||||||
|
|
||||||
|
Regression coverage for #20074: a chat session must export the active kanban
|
||||||
|
board into `HERMES_KANBAN_BOARD` at boot so subprocess shell-outs (e.g.
|
||||||
|
`hermes kanban …`) inherit the same board the in-process kanban tools resolve.
|
||||||
|
Without this, a concurrent `hermes kanban boards switch` from another session
|
||||||
|
can flip the global current-board file mid-turn and silently divert the
|
||||||
|
shell calls to a different DB.
|
||||||
|
"""
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
|
||||||
|
def test_pin_writes_resolved_board_when_env_unset(monkeypatch):
|
||||||
|
monkeypatch.delenv("HERMES_KANBAN_BOARD", raising=False)
|
||||||
|
main_mod = importlib.import_module("hermes_cli.main")
|
||||||
|
|
||||||
|
import hermes_cli.kanban_db as kdb
|
||||||
|
monkeypatch.setattr(kdb, "get_current_board", lambda: "space")
|
||||||
|
|
||||||
|
main_mod._pin_kanban_board_env()
|
||||||
|
|
||||||
|
assert main_mod.os.environ.get("HERMES_KANBAN_BOARD") == "space"
|
||||||
|
|
||||||
|
|
||||||
|
def test_pin_does_not_overwrite_existing_env(monkeypatch):
|
||||||
|
monkeypatch.setenv("HERMES_KANBAN_BOARD", "preset")
|
||||||
|
main_mod = importlib.import_module("hermes_cli.main")
|
||||||
|
|
||||||
|
import hermes_cli.kanban_db as kdb
|
||||||
|
|
||||||
|
def _explode():
|
||||||
|
raise AssertionError("get_current_board must not be called when env is set")
|
||||||
|
|
||||||
|
monkeypatch.setattr(kdb, "get_current_board", _explode)
|
||||||
|
|
||||||
|
main_mod._pin_kanban_board_env()
|
||||||
|
|
||||||
|
assert main_mod.os.environ.get("HERMES_KANBAN_BOARD") == "preset"
|
||||||
|
|
||||||
|
|
||||||
|
def test_pin_swallows_resolution_failures(monkeypatch):
|
||||||
|
monkeypatch.delenv("HERMES_KANBAN_BOARD", raising=False)
|
||||||
|
main_mod = importlib.import_module("hermes_cli.main")
|
||||||
|
|
||||||
|
import hermes_cli.kanban_db as kdb
|
||||||
|
|
||||||
|
def _boom():
|
||||||
|
raise RuntimeError("disk gone")
|
||||||
|
|
||||||
|
monkeypatch.setattr(kdb, "get_current_board", _boom)
|
||||||
|
|
||||||
|
main_mod._pin_kanban_board_env()
|
||||||
|
|
||||||
|
assert "HERMES_KANBAN_BOARD" not in main_mod.os.environ
|
||||||
Loading…
Add table
Add a link
Reference in a new issue