You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(repo-hygiene): keep the read-only scan off the network so it never pops git prompts #6039
The read-only scan behind /repo-hygiene:clean (and every repo of clean-batch --tier scan) runs a networked git command with nothing making it non-interactive. On Windows, Git for Windows routes SSH host-key confirmation and credential requests to GUI dialogs, so a scan of a repo whose remote is unknown, unreachable, or needs credentials opens a modal dialog on the user's desktop. An unattended fleet run blocks on it.
plugins/repo-hygiene/skills/clean/scripts/scan.sh:92: STALE_REFS="$(git remote prune origin --dry-run 2>/dev/null | head -5 | tr '\n' '; ')". git remote prune --dry-run contacts the remote. No GIT_TERMINAL_PROMPT=0, no </dev/null, no SSH batch mode. 2>/dev/null hides error text, not a GUI prompt.
plugins/repo-hygiene/skills/clean/scripts/clean-batch.sh:299 runs scan.sh once per repo for --tier scan.
plugins/repo-hygiene/skills/clean/scripts/git-branch-audit.sh:362 (the --remote path): GIT_TERMINAL_PROMPT=0 git -C "$REPO_ROOT" ls-remote --heads origin 2>/dev/null </dev/null. Better, but still incomplete: GIT_TERMINAL_PROMPT=0 does not stop an SSH host-key or passphrase prompt (SSH asks through SSH_ASKPASS, which Git for Windows points at git-askpass.exe), and does not cover a credential-helper GUI.
Acting paths, attended but with the same exposure: git-prune.sh (--apply runs git remote prune origin, listed in lib/cleanup-paths.sh:58) and git-tree-reset.sh:199 (git fetch "$UPSTREAM_REMOTE").
Evidence
Verified this pass (read-only on origin/main):
The lines above.
The caller that produced the observed dialog: plugins/repo-hygiene/skills/clean/scripts/clean-batch.test.sh:505 sets a fixture repo's origin to exactly git@GitHub.com:owner/repo.git, and the --tier scan --fleet cases that follow (around :537-541) run scan.sh on it, which reaches scan.sh:92. The HTTPS fixtures at :506 (https://user@github.com/owner/repo) and :507 (https://github.com/owner/other.git) go through the same path and can reach the credential helper.
clean-batch.sh:690 already reports the git tier's shared object store with "remote prune not measured", so the batch report has a precedent for not measuring the remote.
Observed by the producing session on 2026-10-03 (Windows, Git Bash), process trace of one dialog:
git-askpass.exe "The authenticity of host 'github.com (140.82.114.3)' can't be established.
ED25519 key fingerprint is: SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU
This key is not known by any other names. Are you sure you want to continue connecting ...?"
<- ssh.exe ssh -o SendEnv=GIT_PROTOCOL git@GitHub.com "git-upload-pack 'owner/repo.git'"
<- git.exe git remote prune origin --dry-run
<- bash.exe .../plugins/repo-hygiene/skills/clean/scripts/scan.sh
The GitHub.com spelling was not in known_hosts, so SSH asked through the GUI askpass for a repository that does not exist.
Not verified: which setting reliably suppresses Git Credential Manager's GUI for HTTPS remotes (candidates: GCM_INTERACTIVE=never, -c credential.interactive=false, an empty GIT_ASKPASS). Confirm against GCM's docs before relying on one.
Proposed approach
Recommended, fewest moving parts first:
Make scan.sh local-only. Drop the git remote prune origin --dry-run call and report stale remote-tracking refs from local state only, or print "remote prune not measured" as clean-batch.sh:690 already does. The scan is the read-only inventory; git-prune.sh --apply is the step that acts and may contact the remote.
For the remaining networked calls (git-branch-audit.sh:362, git-prune.sh --apply, git-tree-reset.sh:199), add one shared helper in skills/clean/scripts/lib/ that runs git non-interactively: GIT_TERMINAL_PROMPT=0, stdin from /dev/null, SSH batch mode with a connect timeout, and a credential-helper no-GUI setting once verified. On failure, report "remote unreachable" rather than hang.
Tests use a local file:// bare repository as any fixture remote that is actually contacted, and the dedupe fixtures in clean-batch.test.sh:505-507 stay as URL strings that nothing contacts once 1 lands.
Alternative considered: keep the network call in scan.sh but guard it with the helper. Rejected as the default because a read-only inventory gains little from a live remote check and a guarded call still costs a connection per repo in a fleet run.
Files: plugins/repo-hygiene/skills/clean/scripts/scan.sh, scan.test.sh, lib/ (new helper), git-branch-audit.sh, git-prune.sh, git-tree-reset.sh, clean-batch.test.sh, and the skill text if it describes the scan's stale-refs line.
Acceptance criteria
scan.sh on a repo whose origin is git@unknown-host.invalid:owner/repo.git finishes with exit 0, prints a stale-refs line that does not depend on the network, and starts no ssh, git-askpass, or ssh-askpass process. Test setup: set GIT_SSH_COMMAND to a recording shim (or point GIT_CONFIG_GLOBAL at a test config whose core.sshCommand is the shim) and set GIT_CONFIG_NOSYSTEM=1, so the shim records any connection even on a host whose global core.sshCommand is an absolute path that a PATH shim would never intercept. The test must fail on current main.
clean-batch.sh --tier scan --fleet over the existing fixtures, under the same shim setup, records no ssh invocation and reaches no credential helper.
Every remaining networked git call in the plugin goes through the shared non-interactive helper, and a test proves an unreachable ssh remote yields a "remote unreachable" style result without hanging.
The helper keeps the user's configured core.sshCommand (a test sets core.sshCommand to a recording shim and asserts the shim is what runs, with BatchMode=yes in its arguments).
The repo-hygiene test suite passes with networking unavailable.
The test-only GIT_SSH_COMMAND or GIT_CONFIG_GLOBAL override above exists to intercept connections in tests; it does not contradict the production rule against replacing core.sshCommand.
GIT_TERMINAL_PROMPT=0 alone stops neither the SSH host-key prompt nor a GUI credential helper.
Version bump and CHANGELOG entry for repo-hygiene.
Context
Source: local handoff item 20261003-053544-repo-hygiene-scan-network-prompts.md (retired into this issue). Related: #4211 (closed; the core.sshCommand and credential.helper lesson).
Problem
The read-only scan behind
/repo-hygiene:clean(and every repo ofclean-batch --tier scan) runs a networked git command with nothing making it non-interactive. On Windows, Git for Windows routes SSH host-key confirmation and credential requests to GUI dialogs, so a scan of a repo whose remote is unknown, unreachable, or needs credentials opens a modal dialog on the user's desktop. An unattended fleet run blocks on it.On main (repo-hygiene 0.18.6, commit 6f59096):
plugins/repo-hygiene/skills/clean/scripts/scan.sh:92:STALE_REFS="$(git remote prune origin --dry-run 2>/dev/null | head -5 | tr '\n' '; ')".git remote prune --dry-runcontacts the remote. NoGIT_TERMINAL_PROMPT=0, no</dev/null, no SSH batch mode.2>/dev/nullhides error text, not a GUI prompt.plugins/repo-hygiene/skills/clean/scripts/clean-batch.sh:299runsscan.shonce per repo for--tier scan.plugins/repo-hygiene/skills/clean/scripts/git-branch-audit.sh:362(the--remotepath):GIT_TERMINAL_PROMPT=0 git -C "$REPO_ROOT" ls-remote --heads origin 2>/dev/null </dev/null. Better, but still incomplete:GIT_TERMINAL_PROMPT=0does not stop an SSH host-key or passphrase prompt (SSH asks throughSSH_ASKPASS, which Git for Windows points atgit-askpass.exe), and does not cover a credential-helper GUI.git-prune.sh(--applyrunsgit remote prune origin, listed inlib/cleanup-paths.sh:58) andgit-tree-reset.sh:199(git fetch "$UPSTREAM_REMOTE").Evidence
Verified this pass (read-only on
origin/main):plugins/repo-hygiene/skills/clean/scripts/clean-batch.test.sh:505sets a fixture repo's origin to exactlygit@GitHub.com:owner/repo.git, and the--tier scan --fleetcases that follow (around:537-541) runscan.shon it, which reachesscan.sh:92. The HTTPS fixtures at:506(https://user@github.com/owner/repo) and:507(https://github.com/owner/other.git) go through the same path and can reach the credential helper.clean-batch.sh:690already reports the git tier's shared object store with "remote prune not measured", so the batch report has a precedent for not measuring the remote.core.sshCommand(Windows OpenSSH, which reaches the Windows SSH agent) and the systemcredential.helper.Observed by the producing session on 2026-10-03 (Windows, Git Bash), process trace of one dialog:
The
GitHub.comspelling was not inknown_hosts, so SSH asked through the GUI askpass for a repository that does not exist.Not verified: which setting reliably suppresses Git Credential Manager's GUI for HTTPS remotes (candidates:
GCM_INTERACTIVE=never,-c credential.interactive=false, an emptyGIT_ASKPASS). Confirm against GCM's docs before relying on one.Proposed approach
Recommended, fewest moving parts first:
scan.shlocal-only. Drop thegit remote prune origin --dry-runcall and report stale remote-tracking refs from local state only, or print "remote prune not measured" asclean-batch.sh:690already does. The scan is the read-only inventory;git-prune.sh --applyis the step that acts and may contact the remote.git-branch-audit.sh:362,git-prune.sh --apply,git-tree-reset.sh:199), add one shared helper inskills/clean/scripts/lib/that runs git non-interactively:GIT_TERMINAL_PROMPT=0, stdin from/dev/null, SSH batch mode with a connect timeout, and a credential-helper no-GUI setting once verified. On failure, report "remote unreachable" rather than hang.file://bare repository as any fixture remote that is actually contacted, and the dedupe fixtures inclean-batch.test.sh:505-507stay as URL strings that nothing contacts once 1 lands.Alternative considered: keep the network call in
scan.shbut guard it with the helper. Rejected as the default because a read-only inventory gains little from a live remote check and a guarded call still costs a connection per repo in a fleet run.Files:
plugins/repo-hygiene/skills/clean/scripts/scan.sh,scan.test.sh,lib/(new helper),git-branch-audit.sh,git-prune.sh,git-tree-reset.sh,clean-batch.test.sh, and the skill text if it describes the scan's stale-refs line.Acceptance criteria
scan.shon a repo whose origin isgit@unknown-host.invalid:owner/repo.gitfinishes with exit 0, prints a stale-refs line that does not depend on the network, and starts nossh,git-askpass, orssh-askpassprocess. Test setup: setGIT_SSH_COMMANDto a recording shim (or pointGIT_CONFIG_GLOBALat a test config whosecore.sshCommandis the shim) and setGIT_CONFIG_NOSYSTEM=1, so the shim records any connection even on a host whose globalcore.sshCommandis an absolute path that a PATH shim would never intercept. The test must fail on current main.clean-batch.sh --tier scan --fleetover the existing fixtures, under the same shim setup, records no ssh invocation and reaches no credential helper.sshremote yields a "remote unreachable" style result without hanging.core.sshCommand(a test setscore.sshCommandto a recording shim and asserts the shim is what runs, withBatchMode=yesin its arguments).Constraints and gotchas
GIT_SSH_COMMAND="ssh -o BatchMode=yes"as a fixed string:GIT_SSH_COMMANDoverridescore.sshCommand. On a Windows machine where the globalcore.sshCommandisC:/Windows/System32/OpenSSH/ssh.exe, that silently swaps in Git for Windows' bundled ssh, which cannot reach the Windows SSH agent, so every authenticated remote fails. This is the failure repo-fleet-hygiene:audit: every ls-remote probe fails because run_git_probe discards global core.sshCommand, so merged-remote-branch never reaches HIGH #4211 hit. Resolvegit config core.sshCommand(defaultssh) and append-o BatchMode=yes -o ConnectTimeout=5to it.GIT_CONFIG_GLOBAL=/dev/null) for the same reason; repo-fleet-hygiene:audit: every ls-remote probe fails because run_git_probe discards global core.sshCommand, so merged-remote-branch never reaches HIGH #4211 also lostcredential.helperthat way.GIT_SSH_COMMANDorGIT_CONFIG_GLOBALoverride above exists to intercept connections in tests; it does not contradict the production rule against replacingcore.sshCommand.GIT_TERMINAL_PROMPT=0alone stops neither the SSH host-key prompt nor a GUI credential helper.Context
Source: local handoff item 20261003-053544-repo-hygiene-scan-network-prompts.md (retired into this issue). Related: #4211 (closed; the
core.sshCommandandcredential.helperlesson).