Skip to content

fix(init): target the session's active directory, not the process cwd - #96378

Merged
OutThisLife merged 2 commits into
NousResearch:mainfrom
funkyotc:fix/init-session-cwd
Oct 1, 2026
Merged

OutThisLife merged 2 commits into
NousResearch:mainfrom
funkyotc:fix/init-session-cwd

Conversation

@funkyotc

@funkyotc funkyotc commented Aug 27, 2026 •

Copy link
Copy Markdown

What does this PR do?

/init resolved its project directory via os.getcwd() — the process launch directory — on all three dispatch surfaces (CLI, gateway, TUI/desktop). On the desktop app the process launches from the home directory, so /init scanned the HOME and merge-updated the home directory's AGENTS.md even though the session was attached to a project workspace (and its terminal commands ran in the right directory).

This PR makes /init target the session's ACTIVE directory:

  1. The terminal tool's per-session cwd record (tools.terminal_tool.get_session_cwd) — seeded when a workspace attaches to the session (desktop project picker, project_create/project_switch, gateway terminal.cwd) and updated after every completed command, so it tracks cd. Keyed by session_key; dispatch sites pass it so multi-session hosts consult the right record.
  2. agent.runtime_cwd.resolve_agent_cwd() — the codebase's canonical ladder (session contextvar → TERMINAL_CWD → process cwd), already used by the system prompt and tool surfaces.

A record for a deleted directory (removed project, linked worktree) falls through to the ladder on every surface — the guard lives in build_init_prompt_for_cwd(), so no dispatch site can hand a truthy-but-dead cwd straight through. An explicit cwd still wins while it exists (tests pass it directly).

Related Issue

Fixes #96376

Related (sibling, distinct): #76902

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/init_command.py — new _resolve_session_cwd() (record → resolve_agent_cwd() ladder); build_init_prompt_for_cwd() gains a session_key param and uses the resolver instead of os.getcwd(); an explicit cwd that no longer exists falls through to that resolver
  • hermes_cli/cli_commands_mixin.py — CLI /init passes session_key="" (single-session CLI's "default" record; previous behavior preserved when no record exists)
  • gateway/run_inbound.py — gateway /init (_hm_cmd_init) passes session_key=_quick_key or self._session_key_for_source(source)
  • tui_gateway/methods_tools.py — TUI/desktop /init becomes a dedicated _cmd_init handler (replacing the generic _prompt_builtin factory): the session's live cwd record when it still exists, else the session's attached workspace (_session_cwd), else the builder's ladder
  • tests/hermes_cli/test_init_command.py — 5 regression tests (session record, stale-record fallback, explicit-cwd precedence, ambient session key, stale explicit cwd fall-through)
  • tests/tui_gateway/test_init_targets_session_cwd.py — new: drives the real command.dispatch handler for /init (live record, and a stale record falling through to the session workspace)

How to Test

  1. Attach a project workspace to a desktop session (project picker) while the process cwd is elsewhere (e.g. home dir — the default on desktop).
  2. Run /init in that session.
  3. The injected prompt now says for the project at: <project> and reads the project's AGENTS.md for merge, not the home directory's.
  4. pytest tests/hermes_cli/test_init_command.py tests/tui_gateway/test_init_targets_session_cwd.py -q — 13 passed (6 pre-existing + 5 builder regressions + 2 TUI/desktop dispatches).

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — targeted suites re-run on this rebase onto current main: test_init_command.py + test_init_targets_session_cwd.py (13 passed), and the other command.dispatch consumers test_goal_dispatch.py + test_compress_lock_skip.py + test_composite_carrier_rewind.py (90 passed)
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11 (native, desktop app repro + pytest on the patched tree)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — docstrings on the resolver + build_init_prompt_for_cwd
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A (no config keys)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — resolution uses the existing platform-agnostic session-cwd machinery; os.path paths only
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A (no model-tool changes; /init has no engine)

Screenshots / Logs

Repro evidence is in the issue: session 20260827_212218_17afe7 — session row recorded cwd = C:\Users\funky\AppDev\infinite-jukebox while the injected /init prompt targeted C:\Users\funky (home dir). After this patch the resolver returns the session record first.

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 27, 2026
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference, author can ignore or act on any point.

This PR makes /init resolve the session's active directory (terminal per-session cwd record, then the resolve_agent_cwd() ladder) instead of os.getcwd(), so a desktop session attached to a workspace scans/writes that workspace's AGENTS.md rather than the home dir the process launched from. The design and tests are solid; one edge case leaks through.

  • The stale-record fall-through is not applied on the TUI path. hermes_cli/init_command.py:167 (if recorded and os.path.isdir(recorded)) correctly skips a deleted-directory record inside _resolve_session_cwd, and test_stale_record_falls_back_to_process_cwd covers it. But tui_gateway/methods_tools.py:606 calls get_session_cwd(_init_skey) and passes the result straight through as cwd at tui_gateway/methods_tools.py:617 — and get_session_cwd (tools/terminal_tool.py:1215) does not validate the directory. Since build_init_prompt_for_cwd uses cwd if cwd else _resolve_session_cwd(session_key), a truthy but deleted record wins on the desktop/TUI surface, contradicting the PR's stated "stale record (directory deleted) falls through to the ladder". Suggest applying the same os.path.isdir guard before passing cwd, or moving the existence check inside build_init_prompt_for_cwd so all three dispatch surfaces share it. A regression test mirroring test_stale_record_falls_back_to_process_cwd but driving the TUI path (stale record + _session_cwd empty) would pin this.

  • Minor: gateway/run.py passes session_key=self._session_key_for_source(source); it would be worth confirming that key matches the session_key value the terminal tool records cwds under for gateway sessions (the tests use a literal "gateway:999"), so the record is actually consulted rather than silently missed.

@funkyotc
funkyotc force-pushed the fix/init-session-cwd branch from 43c115e to 8e19574 Compare September 10, 2026 08:04
@funkyotc

Copy link
Copy Markdown
Author

Rebased onto current main (the branch had gone CONFLICTING) and addressed both points.

1. Stale-record guard — fixed, at the shared point. You were right: get_session_cwd does not validate the directory, and the TUI/desktop path passed its result straight through as cwd, where build_init_prompt_for_cwd's cwd if cwd else … short-circuited before _resolve_session_cwd's os.path.isdir check could run. The check now lives in build_init_prompt_for_cwd (cwd if cwd and os.path.isdir(cwd)), so all three surfaces share it, and the TUI/desktop _cmd_init additionally falls back to the session's attached workspace (_session_cwd) when the record is dead — otherwise a stale record would lose the session's own (healed) cwd to the generic ladder.

One consequence your review implies but doesn't state: without the guard the prompt lands in generate mode pointing at a path that doesn't exist, and write_file creates parent directories — so /init would have recreated a deleted project directory and written a fresh AGENTS.md into it.

Pinned by two new tests: a stale explicit cwd falling through to the session record (builder level), and a new tests/tui_gateway/test_init_targets_session_cwd.py that drives the real command.dispatch handler for both the live-record and stale-record cases — the TUI path you asked to mirror.

2. Gateway session key — confirmed, no gap. _session_key_for_source is the key the gateway uses throughout: inbound turns bind session_key=context.session_key via set_session_vars in gateway/run.py, and that value comes from self._session_key_for_source(source) in run_inbound.py; the terminal tool records cwds under _current_session_key() — the HERMES_SESSION_KEY contextvar — written by finalize_foreground_result through record_session_cwd. Same value, so the record is consulted rather than silently missed. The "gateway:999" literal in the tests is just a synthetic key. The handler now passes _quick_key (that same value) with _session_key_for_source(source) as the fallback.

Test evidence on the rebase (Windows 11): tests/hermes_cli/test_init_command.py + tests/tui_gateway/test_init_targets_session_cwd.py → 13 passed; the other command.dispatch consumers (test_goal_dispatch.py, test_compress_lock_skip.py, test_composite_carrier_rewind.py) → 90 passed. Head is now 8e1957489 and the PR reports MERGEABLE.

/init resolved its project directory via os.getcwd() — the process
launch directory — on all three dispatch surfaces (CLI, gateway,
TUI/desktop). On the desktop app the process launches from the home
directory, so /init scanned the HOME and merge-updated the home
directory's AGENTS.md even though the session was attached to a
project workspace (its terminal commands ran in the right directory).

Resolve the session's ACTIVE directory instead: the terminal tool's
per-session cwd record (seeded when a workspace attaches, updated
after every cd) first, then resolve_agent_cwd()'s canonical ladder
(session contextvar -> TERMINAL_CWD -> process cwd). Dispatch sites
pass the session key so multi-session hosts consult the right record.

The stale-record guard lives in build_init_prompt_for_cwd: a cwd that
names a deleted directory (removed project, linked worktree) falls
through to the session ladder on every surface instead of each
dispatch site validating its own record. Without it the TUI/desktop
path handed a truthy-but-dead record straight through as cwd, so /init
targeted a directory that no longer existed — write_file would have
recreated it and written a fresh AGENTS.md into it.

Regression tests cover: session record wins over the process cwd,
stale-record fallback, explicit cwd precedence, ambient session key,
a stale explicit cwd falling through to the session record, and the
TUI/desktop command.dispatch path end-to-end (live record, and a
stale record falling through to the session's attached workspace).

Fixes NousResearch#96376
OutThisLife
OutThisLife previously approved these changes Sep 29, 2026

@OutThisLife OutThisLife left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed on current main: the diff covers the linked issue, and the new tests fail on main and pass here.

@OutThisLife
OutThisLife enabled auto-merge (rebase) September 29, 2026 01:23
auto-merge was automatically disabled September 29, 2026 16:39

Head branch was pushed to by a user without write access

@OutThisLife OutThisLife left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the new head: adds only the contributor email mapping.

@OutThisLife
OutThisLife enabled auto-merge (rebase) September 29, 2026 16:40
@OutThisLife OutThisLife closed this Oct 1, 2026
auto-merge was automatically disabled October 1, 2026 06:39

Pull request was closed

@OutThisLife OutThisLife reopened this Oct 1, 2026
@OutThisLife
OutThisLife enabled auto-merge (rebase) October 1, 2026 06:40
@OutThisLife
OutThisLife merged commit bafb42b into NousResearch:main Oct 1, 2026
119 of 128 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: /init targets the process cwd (home dir on desktop) instead of the session's active directory

4 participants