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
[Bug]: git extension: non-dry-run git fetch can block /speckit.specify on a hidden credential prompt #5199
The git extension's feature-branch scripts run git fetch --all --prune without GIT_TERMINAL_PROMPT=0 when --dry-run isn't set. The dry-run path already sets it for git ls-remote, but the normal path doesn't:
extensions/git/scripts/powershell/create-new-feature-branch.ps1 line 166: git fetch --all --prune 2>$null | Out-Null
extensions/git/scripts/python/create_new_feature_branch.py line 270: subprocess.run(["git", "fetch", "--all", "--prune"], ...) with no env
speckit.git.feature runs as the mandatory before_specify hook, so this fetch runs on every /speckit.specify. If any remote is HTTPS and has no usable credential helper, git asks for a username on the terminal. Git writes that prompt to the tty, so the output redirect doesn't hide it, and the command waits for input. In an agent-driven session, /speckit.specify then appears to hang.
Steps to Reproduce
git init a scratch repo and add an HTTPS remote that needs authentication, for example a private or nonexistent repo: git remote add origin https://github.com/<owner>/<private-repo>.git.
In a terminal, run git -c credential.helper= fetch --all --prune >/dev/null 2>&1.
Run the same command with GIT_TERMINAL_PROMPT=0 in front.
Expected Behavior
The fetch fails fast and the script continues (|| true), as the dry-run path already does for ls-remote.
Actual Behavior
Step 2 prints Username for 'https://github.com': despite the redirect and blocks until input arrives. Step 3 exits 128 immediately with no prompt.
Specify CLI Version
Seen on 1.2.1. The same lines are in v1.2.5 and on main.
AI Agent
Not agent-specific (git extension scripts).
Operating System
macOS
Python Version
3.15
Error Logs
Username for'https://github.com':
Additional Context
Suggested fix: set GIT_TERMINAL_PROMPT=0 on all three fetch calls (GIT_TERMINAL_PROMPT=0 git fetch ... in bash, $env:GIT_TERMINAL_PROMPT = '0' scoped around the call in PowerShell, env={**os.environ, "GIT_TERMINAL_PROMPT": "0"} in Python). SSH remotes are not covered by this variable; GIT_SSH_COMMAND="ssh -o BatchMode=yes" would be the equivalent if passphrase prompts should also be suppressed.
AI Disclosure
Drafted with Claude Code (model claude-opus-5-5), human-supervised. An AI code review first flagged the missing variable; I reproduced the prompt locally with the steps above before filing.
Issue #5199 reports that /speckit.specify can appear to hang when the git extension executes a non-dry-run git fetch --all --prune against an HTTPS remote that requires credentials but has no usable helper. The report includes a minimal reproduction showing that redirecting stdout/stderr does not suppress terminal credential prompts, while GIT_TERMINAL_PROMPT=0 makes the command fail fast.
Repository evidence matches the report: dry-run remote probing uses GIT_TERMINAL_PROMPT=0 for ls-remote, while non-dry-run fetch paths do not set this variable in Bash, PowerShell, or Python.
Symptom
When speckit.git.feature runs before speckit.specify, the non-dry-run fetch can block on an interactive username/password prompt from git, causing /speckit.specify to stall in agent/non-interactive sessions. Expected behavior is fast failure (exit code) with continued script flow, not an interactive wait.
Reproduction
Initialize a scratch repository and configure an HTTPS remote that requires authentication (or is otherwise credential-challenging).
Run git -c credential.helper= fetch --all --prune >/dev/null 2>&1.
Observe the terminal prompt (for example Username for 'https://github.com':) and blocking behavior.
Run GIT_TERMINAL_PROMPT=0 git -c credential.helper= fetch --all --prune >/dev/null 2>&1 and observe immediate non-interactive failure.
Trigger /speckit.specify with the git extension enabled so the before_specify hook executes speckit.git.feature.
extensions/git/scripts/python/create_new_feature_branch.py:526 — non-dry-run main flow calls check_existing_branches(...), entering the unguarded fetch path.
extensions/git/extension.yml:47-49 — before_specify hook for speckit.git.feature is optional: false, so this path is routinely exercised when the extension is enabled.
Root Cause Hypothesis
The script family intentionally hardens dry-run remote inspection (ls-remote) against interactive prompts, but the corresponding non-dry-run fetch path was not given the same non-interactive environment. Because git credential prompts are tty-directed and not reliably suppressed by redirecting stdout/stderr, these fetch calls can block execution despite output redirection. Confidence: high.
Proposed Remediation
Preferred: apply a consistent non-interactive guard to all non-dry-run fetch calls used for branch-number discovery. In Bash, prefix fetch with GIT_TERMINAL_PROMPT=0; in PowerShell, scope $env:GIT_TERMINAL_PROMPT='0' around the fetch and restore prior value; in Python, pass env={**os.environ, "GIT_TERMINAL_PROMPT": "0"} (or existing helper equivalent) to subprocess.run. Keep existing behavior of tolerating fetch failure (|| true / equivalent) so offline and restricted environments continue gracefully.
Alternatives (optional):
Skip fetch entirely and rely only on local refs + specs in non-dry-run mode. Trade-off: avoids prompt risk but can allocate branch numbers behind remote state more often.
Also add SSH non-interactive guard (e.g., batch mode) where appropriate. Trade-off: broader behavior change and higher compatibility risk across user SSH setups.
Add regression tests that assert non-dry-run branch-number discovery invokes fetch with non-interactive environment in all three implementations.
Add parity coverage ensuring dry-run and non-dry-run prompt-handling strategy is consistent across Bash/PowerShell/Python.
Add a behavior test that simulates credential-challenging remotes and verifies command returns promptly instead of hanging (mock/stub subprocess or git wrapper in deterministic test harness).
Risks & Considerations
PowerShell env scoping must avoid leaking GIT_TERMINAL_PROMPT to later commands.
Enforcing non-interactive mode may change user-visible errors from prompt-driven retries to immediate failures; this is desirable for automation but should be documented in test expectations.
Ensure no regression in offline/no-git graceful-degradation paths.
Open Questions
[NEEDS CLARIFICATION: Should SSH auth/passphrase prompts also be suppressed in this path for full non-interactive parity, or is HTTPS credential prompt suppression the intended scope?]
Activity
github-actions commentedon Oct 30, 2026
**Bug assessment — git-fetch-prompt-block: Valid · severity medium
Bug Assessment: git extension fetch can block on hidden credential prompt
Report (summarized)
Issue #5199 reports that
/speckit.specifycan appear to hang when the git extension executes a non-dry-rungit fetch --all --pruneagainst an HTTPS remote that requires credentials but has no usable helper. The report includes a minimal reproduction showing that redirecting stdout/stderr does not suppress terminal credential prompts, whileGIT_TERMINAL_PROMPT=0makes the command fail fast.Repository evidence matches the report: dry-run remote probing uses
GIT_TERMINAL_PROMPT=0forls-remote, while non-dry-run fetch paths do not set this variable in Bash, PowerShell, or Python.Symptom
When
speckit.git.featureruns beforespeckit.specify, the non-dry-run fetch can block on an interactive username/password prompt from git, causing/speckit.specifyto stall in agent/non-interactive sessions. Expected behavior is fast failure (exit code) with continued script flow, not an interactive wait.Reproduction
git -c credential.helper= fetch --all --prune >/dev/null 2>&1.Username for 'https://github.com':) and blocking behavior.GIT_TERMINAL_PROMPT=0 git -c credential.helper= fetch --all --prune >/dev/null 2>&1and observe immediate non-interactive failure./speckit.specifywith the git extension enabled so thebefore_specifyhook executesspeckit.git.feature.Suspected Code Paths
extensions/git/scripts/bash/create-new-feature-branch.sh:172— dry-run remote check correctly usesGIT_TERMINAL_PROMPT=0forgit ls-remote.extensions/git/scripts/bash/create-new-feature-branch.sh:194— non-dry-run branch-number path callsgit fetch --all --prunewithoutGIT_TERMINAL_PROMPT=0.extensions/git/scripts/bash/create-new-feature-branch.sh:527— non-dry-run main flow callscheck_existing_branches(..., false, ...), taking the interactive fetch path.extensions/git/scripts/powershell/create-new-feature-branch.ps1:135— dry-run remote check sets$env:GIT_TERMINAL_PROMPT='0'forgit ls-remote.extensions/git/scripts/powershell/create-new-feature-branch.ps1:166— non-dry-run path runsgit fetch --all --prunewithout non-interactive guard.extensions/git/scripts/powershell/create-new-feature-branch.ps1:483— non-dry-run main flow callsGet-NextBranchNumberwithout-SkipFetch.extensions/git/scripts/python/create_new_feature_branch.py:252— dry-run remote check usesenv_extra={"GIT_TERMINAL_PROMPT": "0"}forls-remote.extensions/git/scripts/python/create_new_feature_branch.py:269-270— non-dry-run path runssubprocess.run(["git", "fetch", "--all", "--prune"], ...)without env override.extensions/git/scripts/python/create_new_feature_branch.py:526— non-dry-run main flow callscheck_existing_branches(...), entering the unguarded fetch path.extensions/git/extension.yml:47-49—before_specifyhook forspeckit.git.featureisoptional: false, so this path is routinely exercised when the extension is enabled.Root Cause Hypothesis
The script family intentionally hardens dry-run remote inspection (
ls-remote) against interactive prompts, but the corresponding non-dry-run fetch path was not given the same non-interactive environment. Because git credential prompts are tty-directed and not reliably suppressed by redirecting stdout/stderr, these fetch calls can block execution despite output redirection. Confidence: high.Proposed Remediation
Preferred: apply a consistent non-interactive guard to all non-dry-run fetch calls used for branch-number discovery. In Bash, prefix fetch with
GIT_TERMINAL_PROMPT=0; in PowerShell, scope$env:GIT_TERMINAL_PROMPT='0'around the fetch and restore prior value; in Python, passenv={**os.environ, "GIT_TERMINAL_PROMPT": "0"}(or existing helper equivalent) tosubprocess.run. Keep existing behavior of tolerating fetch failure (|| true/ equivalent) so offline and restricted environments continue gracefully.Alternatives (optional):
Files likely to change:
extensions/git/scripts/bash/create-new-feature-branch.shextensions/git/scripts/powershell/create-new-feature-branch.ps1extensions/git/scripts/python/create_new_feature_branch.pytests/extensions/git/test_git_extension_python_parity.pyTests to add or update:
Risks & Considerations
GIT_TERMINAL_PROMPTto later commands.Open Questions