fix(guard): block launchctl submit/bootstrap and wrapped-script gateway restarts - #75972
Merged
Conversation
- Resolve guard cwd against get_session_cwd(session_key); fall back to env.cwd
when no session record exists yet, matching current main's per-session cwd
architecture.
- Make referenced-script reads backend-aware: local read first; if missing,
fall back to env.execute('cat ...') for SSH/Modal/Daytona backends.
- Reuse the recursive scanner in check_gateway_lifecycle so nested cron
wrapper scripts are caught, and resolve relative refs inside a script
against that script's directory.
- Add regression tests for remote-backend reads, two-session cwd, and nested
cron wrappers.
Verification: 80 passed tests/hermes_cli/test_gateway_restart_loop.py;
694 passed tests/cron; ruff + git diff --check clean.
…bel-independent detection Extends the shared _GATEWAY_LIFECYCLE_PATTERN (used by BOTH the cron creation-time guard in cron/lifecycle_guard.py and the terminal execution-time hard-block in tools/terminal_tool.py) so Branch B covers launchctl submit and bootstrap alongside kickstart/unload/load/stop/ restart, and normalizes POSIX shell line continuations before matching so the exact multi-line reported shape in #62891 cannot slip past. Also extends the execution-aware, label-independent detector (contains_launchctl_submit_command, cherry-picked from #63272) to cover launchctl bootstrap, since a neutral label like ai.hermes.svc-reload-tmp defeats any label-anchored regex — the second production reproduction. Regression tests cover both sites, including `launchctl submit -l com.foo -- /path/gateway` and the bootstrap variant, plus outside-gateway pass-through. Branch B regex extension and continuation normalization drawn from PR #62896; bootstrap coverage and test shapes drawn from PR #51003. Co-authored-by: JackJin <1037461232@qq.com> Co-authored-by: joelbrilliant <joelbrilliant1@gmail.com>
Contributor
૮ >ﻌ< ა ci reviewran on 5c7393c ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job3 visual diffs. inline evidence upload failed. Failed to upload diff-1508682a2ae8-boot-ready-diff.png with gh image (exit code 1): Error uploading /home/runner/work/_temp/e2e-evidence/diff-1508682a2ae8-boot-ready-diff.png: step 0 (get upload token): uploadToken not found on repo page — do you have write access to NousResearch/hermes-agent? (or, if NousResearch enforces SAML SSO, authorize at https://github.com/orgs/NousResearch/sso) |
This was referenced Aug 1, 2026
Open
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR body draft — fix/launchctl-submit-guard
Title
fix(gateway): block
launchctl submit/bootstraplifecycle-guard bypass (restart loops)Body
Fixes #62891
A macOS gateway session that was correctly blocked from restarting its own gateway laundered the restart through
launchctl submit, creating a persistent launchd keepalive job that killed/respawned the gateway 9,447 times (plus two later production reproductions: a neutral-labelai.hermes.svc-reload-tmpvariant and a 263-runsubmitof an existing helper script).What this does
Fixes the class, not just the instance — the guard regex is shared between the cron creation-time guard (
cron/lifecycle_guard.py) and the terminal execution-time hard-block (tools/terminal_tool.py), and both sites get all three layers:submitandbootstrapadded to the launchctl verb list, plus POSIX shell line-continuation normalization so the exact multi-line reported command can't slip past[^\n]*. (Drawn from fix(gateway): block launchctl submit in the gateway lifecycle guard #62896 by @jackjin1997; bootstrap coverage from fix(gateway): block launchd restart helper loops #51003 by @joelbrilliant.)launchctl submit/bootstrapis blocked inside the gateway process regardless of label, via shlex-based command parsing (quoted prose andsub""mitobfuscation handled). This closes the neutral-label reproduction that defeats any label-anchored regex. (Cherry-picked from fix: block persistent gateway self-restart jobs #63272 by @John-Lussier, extended to coverbootstrap.)-c, relative-cwd, shebang-executable, nested, and remote-backend scripts are recursively scanned (bounded: 1 MiB, depth 8, regular files only, fail-closed on FIFOs/oversize) at both the terminal hard-block and cron script creation. (Cherry-picked from fix: block persistent gateway self-restart jobs #63272 by @John-Lussier.)Credit
submitextension and shell-continuation normalization, reworked into the consolidated head commit (Co-authored-by).bootstrapverb coverage and gateway-label test shapes, reworked into the consolidated head commit (Co-authored-by).Regression tests (both sites)
launchctl submit -l com.foo -- /path/gateway— blocked in terminal (inside gateway) and in cron scriptslaunchctl bootstrap gui/501 /tmp/com.foo.plist— bootstrap variant, both sitesai.hermes.svc-reload-tmpreproductiontests/hermes_cli/test_gateway_restart_loop.py: 78 passed ·tests/cron: 373 passed · terminal tool suites: 133 passed · ruff clean.Follow-up (not in this PR)
A restart-loop-breaker diagnostic that enumerates external launchd sources (
launchctl listlabels touching the gateway plus recently-submitted jobs) whengateway.restart_loop_guardtrips would surface already-planted jobs like the 9,447-run one. That requires runtime gateway-side work beyond this guard-layer fix; recommend a separate issue referencing joelbrilliant's resolver suggestion in the #62891 thread.Infographic