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
19 changes: 19 additions & 0 deletions cron/lifecycle_guard.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,30 @@ def check_gateway_lifecycle(
surfaces this as a tool error; the CLI prints it in red and exits 1).
"""
combined = prompt or ""
is_python = False
if script:
is_python = Path(script).expanduser().suffix == ".py"
script_text = _read_script_for_scanning(script)
if script_text:
combined = f"{combined}\n{script_text}"

# For Python scripts, skip the shell-script reference walk.
# Python files are executed by the interpreter, never through a POSIX
# shell, so shell-reference analysis produces false positives (e.g.
# pathlib's ``/`` operator is misinterpreted as a path separator).
# The direct command regex scan still catches actual lifecycle commands
# like ``hermes gateway restart`` written in the script text.
if is_python:
if contains_gateway_lifecycle_command(combined):
raise GatewayLifecycleBlocked(
"Blocked: cron job contains a gateway lifecycle command or persistent "
"launchctl submit operation. This is blocked to prevent agent-driven "
"SIGTERM-respawn loops under launchd/systemd supervision "
"(#30719). Run `hermes gateway restart` from a shell outside "
"the running gateway instead."
)
return

script_dir = _resolve_script_directory(script) if script else None
if contains_gateway_lifecycle_command_or_referenced_script(
combined,
Expand Down
24 changes: 23 additions & 1 deletion hermes_cli/update_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3920,7 +3920,29 @@ def _cmd_update_impl(args, gateway_mode: bool):
print(f"⚠ Venv still unhealthy after repair: {detail_after}")
print(" Close all Hermes windows/gateways and re-run: hermes update")
else:
print("✓ Already up to date!")
# A current checkout does NOT imply healthy Node deps either:
# a previous npm install may have failed (EBADENGINE, network
# timeout, interrupted install), leaving node_modules in a
# mixed state. The "Already up to date!" path previously only
# checked the Python venv — mirror the same "repair if broken"
# pattern for Node.js dependencies (#77211).
from hermes_constants import get_default_hermes_root

_shared_root = get_default_hermes_root()
if _m()._npm_lockfile_changed(_shared_root):
print("⚠ Checkout is current, but Node.js dependencies may be stale.")
print("→ Refreshing Node.js dependencies...")
node_failures = _update_node_dependencies()
if node_failures:
print(
f" ⚠ Node.js refresh failed for: {', '.join(node_failures)}"
)
print(" Fix npm and re-run `hermes update`.")
else:
_m()._build_web_ui(_m().PROJECT_ROOT / "web")
print("✓ Node.js dependencies refreshed!")
else:
print("✓ Already up to date!")
if runtime_repaired is not None and not _m()._is_windows():
print()
print(
Expand Down
Loading