Skip to content
Open
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
2 changes: 2 additions & 0 deletions hermes_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12982,6 +12982,7 @@ def _try_termux_fast_cli_launch() -> bool:
provider=getattr(args, "provider", None),
toolsets=getattr(args, "toolsets", None),
usage_file=getattr(args, "usage_file", None),
worktree=getattr(args, "worktree", False),
)
)

Expand Down Expand Up @@ -15139,6 +15140,7 @@ def _export_one(session_id: str):
provider=getattr(args, "provider", None),
toolsets=getattr(args, "toolsets", None),
usage_file=getattr(args, "usage_file", None),
worktree=getattr(args, "worktree", False),
)
)

Expand Down
40 changes: 40 additions & 0 deletions hermes_cli/oneshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def run_oneshot(
provider: Optional[str] = None,
toolsets: object = None,
usage_file: Optional[str] = None,
worktree: bool = False,
) -> int:
"""Execute a single prompt and print only the final content block.

Expand All @@ -187,6 +188,9 @@ def run_oneshot(
cost, token counts, model, api_calls) is written there after the
run — even when the run fails — so pipelines can account for
spend per invocation.
worktree: When True, create an isolated git worktree so the agent's
commits don't land on the current branch (same as ``-w`` in
interactive mode).

Returns the exit code. Caller should sys.exit() with the return.
"""
Expand Down Expand Up @@ -230,6 +234,34 @@ def run_oneshot(
# to its inline/synchronous path. See declare_stateless_channel().
declare_stateless_channel()

# Set up isolated git worktree when -w/--worktree is requested.
_wt_info = None
if worktree:
try:
from cli import (
_cleanup_worktree,
_git_repo_root,
_prune_stale_worktrees,
_setup_worktree,
)

repo = _git_repo_root()
if repo:
_prune_stale_worktrees(repo)
_wt_info = _setup_worktree()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_setup_worktree() prints creation messages to stdout (cli.py:1630-1632), but -z promises stdout contains only the final response. Route this helper's output to stderr before calling it.

if not _wt_info:
sys.stderr.write(
"hermes -z: --worktree failed (are you inside a git repo?).\n"
)
return 1
os.environ["HERMES_CWD"] = _wt_info["path"]
os.environ["TERMINAL_CWD"] = _wt_info["path"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Save the prior CWD environment values and restore them in the final cleanup path. As written, a direct caller retains TERMINAL_CWD after _cleanup_worktree() may delete this path.

except Exception as exc:
sys.stderr.write(
f"hermes -z: --worktree setup failed: {exc}\n"
)
return 1

# Redirect stderr AND stdout to devnull for the entire call tree.
# We'll print the final response to the real stdout at the end.
real_stdout = sys.stdout
Expand Down Expand Up @@ -263,6 +295,14 @@ def run_oneshot(
devnull.close()
except Exception:
pass
# Clean up worktree on exit (unless it has unpushed commits — same
# policy as interactive mode).
if _wt_info is not None:
try:
from cli import _cleanup_worktree
_cleanup_worktree(_wt_info)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_cleanup_worktree() prints both normal cleanup and unpushed-commit notices to stdout (cli.py:1776-1777, 1811). Redirect this call to real_stderr so it cannot contaminate one-shot output.

except Exception:
pass

if failure is not None:
# Re-raise control-flow exceptions so the parent handles them as usual
Expand Down
Loading