fix(update): never block hermes update on a git credential prompt - #73751
Open
Frowtek wants to merge 1 commit into
Open
fix(update): never block hermes update on a git credential prompt#73751Frowtek wants to merge 1 commit into
hermes update on a git credential prompt#73751Frowtek wants to merge 1 commit into
Conversation
58708c7 hardened Hermes's internal git plumbing — MCP catalog installs, plugin install/update, profile distribution staging, worktree base fetches, the desktop review pane — with noninteractive_git_env() + stdin=DEVNULL so a private or misconfigured remote fails fast instead of waiting on a prompt nobody can answer. The update path was not in that sweep, and it is the single place users hit a remote most: `hermes update` and `hermes update --check` run eight remote git operations, none of which passed the env, a timeout, or stdin. Every one of them also uses capture_output=True, so the prompt is invisible: the user sees "→ Fetching updates..." (or "→ Fetching from upstream...") and nothing else, indefinitely. It runs unattended too — desktop auto-update, a supervised backend restarting itself, a launchd/systemd unit — and updating from a fork is a detected, first-class case, so a private fork remote or an expired token is the ordinary trigger. Git Credential Manager on Windows pops its own dialog for the same reason. That fail-fast behavior is already the intended contract here: both fetch error handlers special-case "could not read Username", which is precisely what git prints when prompts are disabled. Wire noninteractive_git_env() + stdin=DEVNULL into all eight: _sync_fork_with_upstream (push), _sync_with_upstream_if_needed (fetch, pull), _cmd_update_check (three fetches), _cmd_update_impl (fetch, pull). Local-only git calls, including `git stash push`, are untouched.
teknium1
reviewed
Jul 30, 2026
teknium1
left a comment
Contributor
There was a problem hiding this comment.
Thanks for tracing the credential-prompt gap. The underlying issue remains on current main, but this PR needs relocation before it can affect the live path.
Problems
927463efcc441060c833aa70c99161115a547583moved the update implementation intohermes_cli/update_cmd.py;hermes_cli/main.py:4987-5043now only re-exports it. The remote calls still lacking hardening are inupdate_cmd.py:1241,:1303,:1343,:1994,:2006,:2017, and:3208, so the changedmain.pycall sites no longer execute.- The new tests cover fork push and upstream sync only. They do not exercise the live
--checkfetch branches (update_cmd.py:1994-2022) or the normal update fetch (:3208-3213).
Suggested changes
- Move the
noninteractive_git_env()import andstdin=subprocess.DEVNULL/env=noninteractive_git_env()arguments to the seven current remote operations inhermes_cli/update_cmd.py. - Add plumbing coverage for the
--checkand primary update-fetch paths; retain local-only git operations unchanged.
This is an automated hermes-sweeper review.
| @@ -66,7 +66,10 @@ | |||
| # any dependency touching ``platform.uname()`` at import time flashes a | |||
| # visible console when this process is windowless (pythonw gateway + every | |||
Contributor
There was a problem hiding this comment.
The update pipeline was moved by 927463efcc441060c833aa70c99161115a547583 into hermes_cli/update_cmd.py; main.py now only re-exports these functions. Move this import and the hardened subprocess arguments to update_cmd.py, otherwise the live remote update calls remain unchanged.
26 tasks
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.
What does this PR do?
58708c7 / #73709 hardened Hermes's internal git plumbing so a private, misconfigured, or auth-requiring remote fails fast instead of blocking on a prompt nobody can answer. Its sweep covered MCP catalog installs, plugin install/update, profile distribution staging, worktree base fetches and the desktop review pane.
The update path was not in it — and it is where users touch a remote most.
hermes updateandhermes update --checkrun eight remote git operations, none of which passed the hardened env, a timeout, or stdin:_sync_fork_with_upstreampush origin main --force-with-lease_sync_with_upstream_if_neededfetch upstream main,pull --ff-only upstream main_cmd_update_checkfetch upstream <branch>,fetch origin <branch>×2_cmd_update_implfetch origin <branch>,pull --ff-only origin <branch>Every one also uses
capture_output=True, so the prompt itself is invisible: the user seesand nothing else, indefinitely. And this runs unattended — desktop auto-update, a supervised backend restarting itself, a launchd/systemd unit — while updating from a fork is a detected, first-class case (
_is_forkprints "⚠ Updating from fork"), so a private fork remote or an expired token is the ordinary trigger, not an exotic one. On Windows, Git Credential Manager pops its own dialog for the same reason.Fail-fast is already the intended contract at these exact call sites: both fetch error handlers special-case
could not read Usernameis precisely what git prints when terminal prompts are disabled — the branch can only be reached onceGIT_TERMINAL_PROMPT=0is set, which is what this PR does.Reproduced against
main(58708c706) with the same per-call-site plumbing assertions the original sweep used:Related Issue
No issue — found by auditing the call-site coverage of 58708c7 (#73709), which introduced
noninteractive_git_env()and listed the sites it wired.Fixes #
Type of Change
Changes Made
hermes_cli/main.py: passstdin=subprocess.DEVNULLandenv=noninteractive_git_env()to all eight remote git invocations in the update path (the table above), and import the helper alongside the existing_subprocess_compatimport.git stash push --include-untracked, where "push" is the stash subcommand and no remote is contacted.tests/hermes_cli/test_noninteractive_git.py: two plumbing tests in the file's existing style, filtering captured runs to remote verbs (fetch/pull/push/clone/ls-remote) so local git calls in the same helper aren't over-constrained.How to Test
origin(orupstream) at a private repo you have no cached credentials for, then runhermes update --check. Before this change it hangs after "→ Fetching from upstream..." with no output; after, git exits immediately and the existing handler prints "✗ Authentication failed — check your git credentials or SSH key."pytest tests/hermes_cli/test_noninteractive_git.py -q→ 13 passed (2 new). Both new tests fail onmainwithout the code change (captured above).subprocess.runinhermes_cli/main.pywhose argv carries a remote git verb now passes the hardened env — 8/8.pytest tests/hermes_cli/ -q -k "update or git" --ignore=tests/hermes_cli/test_gateway.py→ 504 passed / 55 failed, versus baseline 502 passed / the same 55 with the change stashed; the delta is exactly the two new tests. (test_gateway.pyis excluded because it fails to collect on this machine —termios— independently of this change.)Checklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
docs/, docstrings) — N/A (no user-facing contract change; this makes the documented failure path reachable instead of hanging)cli-config.yaml.exampleif I added/changed config keys — N/A (no config keys added or changed)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AGCM_INTERACTIVE=Never, which is what stops Git Credential Manager's dialog on Windows installs;GIT_ASKPASS/SSH_ASKPASSstay untouched so working non-interactive auth still succeeds