fix(kanban): pin worker TERMINAL_CWD to the task workspace (#50348)

_default_spawn launched the worker subprocess with cwd=workspace and set
HERMES_KANBAN_WORKSPACE, but never set TERMINAL_CWD — so the worker inherited
the dispatching gateway's TERMINAL_CWD. That value takes precedence over the
process cwd in two places:

- tools/file_tools.py::_resolve_base_dir — a relative write_file path resolved
  against the gateway user's home instead of the workspace, so artifacts
  silently landed outside the workspace (#41312).
- agent_init's context-file loader — AGENTS.md was discovered relative to the
  gateway's cwd, so under multi-profile dispatch a worker loaded whichever
  gateway won the claim race's AGENTS.md, not the task's (#34619).

Both are the same root cause. Pinning TERMINAL_CWD to the workspace (where the
task's work actually happens) fixes both. Guarded on an existing absolute dir
because file_tools rejects relative/sentinel TERMINAL_CWD values — a non-dir
workspace leaves the inherited value rather than writing a meaningless one.

Closes #34619, closes #41312.
This commit is contained in:
Teknium 2026-06-21 12:43:37 -07:00 committed by GitHub
parent b6d1072408
commit 9630ec6c19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 115 additions and 0 deletions

View file

@ -7298,6 +7298,20 @@ def _default_spawn(
env["HERMES_TENANT"] = task.tenant
env["HERMES_KANBAN_TASK"] = task.id
env["HERMES_KANBAN_WORKSPACE"] = workspace
# Pin TERMINAL_CWD to the task's workspace so the worker's file tools and
# context-file loader anchor on the workspace, not whatever cwd the
# dispatching gateway happened to export. The worker subprocess is already
# launched with cwd=workspace, but TERMINAL_CWD takes precedence over the
# process cwd in both file_tools._resolve_base_dir (#41312 — relative
# write_file paths were landing in the gateway user's home) and
# build_context_files_prompt (#34619 — workers loaded the dispatching
# gateway's AGENTS.md instead of the task's). Setting it to the workspace
# fixes both: the workspace is where the task's work actually happens.
# Only pin a real, absolute directory — file_tools rejects relative /
# sentinel TERMINAL_CWD values, so a non-dir workspace must NOT be set
# here (leave the inherited value rather than write a meaningless one).
if workspace and os.path.isabs(workspace) and os.path.isdir(workspace):
env["TERMINAL_CWD"] = workspace
if task.branch_name:
env["HERMES_KANBAN_BRANCH"] = task.branch_name
if task.current_run_id is not None: