Skip to content

feat: add SSH wrapper for Windows OpenSSH bug workaround#13

Merged
caipira113 merged 1 commit into
mainfrom
devin/1782560912-ssh-wrapper
Jun 27, 2026
Merged

feat: add SSH wrapper for Windows OpenSSH bug workaround#13
caipira113 merged 1 commit into
mainfrom
devin/1782560912-ssh-wrapper

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds ~/.ssh/ssh-wrapper.sh to work around a Windows OpenSSH 9.5 bug where non-interactive SSH crashes with close - IO is still pending on closed socket after successful operations, causing Git to interpret the non-zero exit code as a failure.

The wrapper captures stderr, detects the bug message, and returns exit 0 — unless Permission denied is also present (real auth failure).

dot_gitconfig.tmpl now references the wrapper with a dynamic home path:

sshCommand = sh "{{ .chezmoi.homeDir | replace "\\" "/" }}/.ssh/ssh-wrapper.sh"

instead of the previous direct C:/Windows/System32/OpenSSH/ssh.exe.

.chezmoiignore excludes ssh-wrapper.sh on non-Windows (bug is Windows-specific).

Link to Devin session: https://app.devin.ai/sessions/b78d2510243149569051fae15809fef4
Requested by: @caipira113


Open in Devin Review

@caipira113 caipira113 self-assigned this Jun 27, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@caipira113 caipira113 merged commit df146a1 into main Jun 27, 2026
9 of 10 checks passed

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

Open in Devin Review

Comment thread dot_gitconfig.tmpl
{{- end }}
{{- if eq .chezmoi.os "windows" }}
sshCommand = C:/Windows/System32/OpenSSH/ssh.exe
sshCommand = sh "{{ .chezmoi.homeDir | replace "\\" "/" }}/.ssh/ssh-wrapper.sh"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Path quotes in the SSH command configuration are consumed by Git's config parser, breaking the command for paths with spaces

The file path quotes are stripped by Git's config-file parser (sshCommand = sh "..." at dot_gitconfig.tmpl:35) before the value reaches the shell, so any home directory containing a space causes the SSH command to fail with a word-splitting error.

Impact: Git push/pull/fetch operations silently break on Windows machines where the user profile path contains a space.

Git config quote-stripping mechanism and fix options

Git config uses double quotes as its own quoting syntax (to preserve whitespace and shield comment characters # and ;). When the config file contains:

sshCommand = sh "C:/Users/username/.ssh/ssh-wrapper.sh"

the git config parser sees sh as unquoted text, then " toggles quoted mode, captures C:/Users/username/.ssh/ssh-wrapper.sh as literal text, then " ends quoted mode. The resulting stored value is sh C:/Users/username/.ssh/ssh-wrapper.sh (quotes stripped).

When Git invokes sshCommand, it passes the stored value to sh -c with arguments appended. The shell then word-splits the unquoted path. For a user like C:/Users/John Doe/, the shell sees three tokens: sh, C:/Users/John, Doe/.ssh/ssh-wrapper.sh.

To preserve literal quotes in the value so the shell sees them, the template needs escaped quotes inside a git-config quoted string:

sshCommand = "sh \"{{ .chezmoi.homeDir | replace "\\" "/" }}/.ssh/ssh-wrapper.sh\""

or use the fact that git passes the whole value to sh -c "$val" -- args and restructure accordingly.

Prompt for agents
In dot_gitconfig.tmpl line 35, the double quotes around the ssh-wrapper.sh path are being consumed by Git's own config file parser (Git uses double quotes as config-level quoting delimiters). The stored value ends up without quotes, so the shell word-splits the path when it contains spaces (e.g. a Windows username with a space).

To fix: the literal double quotes must survive git config parsing. Inside a git config value, a literal quote is written as a backslash-quote inside a quoted section. The template line needs to produce something like:

  sshCommand = sh \"C:/Users/username/.ssh/ssh-wrapper.sh\"

so that the stored git config value is sh "C:/Users/username/.ssh/ssh-wrapper.sh" and the shell can correctly handle paths with spaces.

Alternatively, since this wrapper is only used on Windows where Git for Windows provides its own sh, you could avoid the quoting issue entirely by using a different invocation strategy, such as putting the entire command in quotes with escaped inner quotes, or by not relying on shell word splitting at all.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +13 to +23
if [ $exit_code -ne 0 ]; then
if grep -q 'close - IO is still pending on closed socket' "$stderr_file"; then
if grep -q 'Permission denied' "$stderr_file"; then
cat "$stderr_file" >&2
rm -f "$stderr_file"
exit $exit_code
fi
grep -v 'close - IO is still pending on closed socket' "$stderr_file" >&2
rm -f "$stderr_file"
exit 0
fi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Wrapper script suppresses all real SSH errors that happen to co-occur with the IO pending message

The wrapper script at private_dot_ssh/ssh-wrapper.sh:13-23 only checks for Permission denied as a signal of a "real" failure when the IO pending bug message is detected. Other genuine SSH errors (e.g., Host key verification failed, Connection refused, No route to host) that happen to co-occur with the IO pending stderr message would be suppressed and the script would return exit 0. This means certain real SSH failures could be silently swallowed. Whether this matters in practice depends on whether the Windows OpenSSH bug ever produces the IO pending message alongside other real errors.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant