Skip to content

fix(windows): prevent Git Bash NUL redirect files - #65363

Closed
helix4u wants to merge 2 commits into
NousResearch:mainfrom
helix4u:fix/windows-nul-redirection
Closed

fix(windows): prevent Git Bash NUL redirect files#65363
helix4u wants to merge 2 commits into
NousResearch:mainfrom
helix4u:fix/windows-nul-redirection

Conversation

@helix4u

@helix4u helix4u commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

On native Windows, LocalEnvironment intentionally executes commands through Git Bash. Git Bash treats a CMD-style NUL redirect target as a relative filename rather than the null device, so model-authored commands such as git show missing 2>NUL || true can leave a literal Windows-reserved NUL file in the working directory.

This PR normalizes common CMD-style NUL redirection targets to /dev/null before the Windows Git Bash command is prepared. Literal quoted text, escaped characters, and comments are left untouched; executable backtick and $(...) substitutions remain eligible for normalization, including inside double quotes. Commands containing heredocs are conservatively left unchanged so redirect-looking payload data is never modified. Non-Windows command handling is unchanged.

Related Issue

Related to #57081. #57212 guards the downstream update/autostash path when a reserved file already exists; this PR prevents the local terminal path from creating one through CMD-style null redirection.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Added Windows Git Bash NUL redirect normalization in tools/environments/local.py.
  • Applied the normalization through LocalEnvironment._prepare_command() only on Windows.
  • Kept executable backtick and $(...) command substitutions visible to the matcher while protecting literal shell text.
  • Conservatively skipped commands containing heredocs so quoted and unquoted payloads cannot be rewritten; here-strings remain eligible for normal redirect handling.
  • Added regression coverage in tests/tools/test_local_env_windows_msys.py for common redirect forms, executable command substitutions, protected quoted/escaped/commented text, quoted and unquoted heredocs, here-strings, non-redirect lookalikes, and the Windows-only environment path.

How to Test

  1. On native Windows, create a temporary Git repository and execute git show missing 2>NUL || true through LocalEnvironment.
  2. Verify that the command completes and no literal NUL file exists in the working directory.
  3. Run scripts/run_tests.sh -j 4 tests/tools/test_local_env_windows_msys.py -k WindowsBashNulRedirections -q.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Windows 11

Full-suite coverage is left to GitHub CI per the repository workflow.

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A (docstrings updated; no user-facing documentation change needed)
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A (terminal schema is unchanged)

Screenshots / Logs

  • Focused regression selection: 7 tests passed, 0 failed with 4 workers.
  • Native Windows Git Bash checks in a temporary directory: executable backtick redirection was normalized without creating a literal NUL file, and quoted heredoc payload output remained exactly 2>NUL without creating a file.
  • Wrapper bytecode pre-compilation and git diff --check pass.
  • A broader native-Windows run of tests/tools/test_local_env_windows_msys.py reached 42 passing tests and two pre-existing path-separator assertion failures outside this change; full-suite coverage is left to GitHub CI.

@helix4u
helix4u marked this pull request as ready for review July 16, 2026 03:56
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists backend/local Local shell execution platform/windows Native Windows-specific behavior or breakage sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Jul 16, 2026
@ethernet8023

Copy link
Copy Markdown
Collaborator

This feels like a band-aid fix to me. Models writing >NUL in commands tells me that we're telling it "you're on windows" in a way that makes it unaware it's using git bash.

Rather than this, can we look into where we tell the model its environment, and try to make it more specific about what exact kind of terminal execution it'll be running in?

@helix4u

helix4u commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

I dug through the persisted Hermes session history, and the model was already being told this.

The offending session’s saved system prompt explicitly says:

“your terminal tool runs commands through bash (git-bash / MSYS), NOT PowerShell or cmd.exe. Use POSIX shell syntax...”

That hint has been present in agent/prompt_builder.py since May 8. Despite receiving it, gpt-5.6-sol ran this from the Windows AppData repo root on July 12:

git show --stat --oneline 11b9b146f 2>NUL || true

The preceding git status showed no NUL; the next one showed ?? NUL. Another Hermes support-investigation session emitted a different 2>NUL command from the same repo root the following day.

So I agree that the model should know it is using Git Bash, but that is already in the prompt and was ignored in the actual sessions that created the file. We could make the prompt even more explicit by mentioning /dev/null, but prompt guidance alone is not deterministic. This PR handles the unambiguous null-device redirect at the execution boundary so a missed instruction cannot create a Windows-reserved file.

@helix4u

helix4u commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

I'll test some more explicit "use /dev/null, never NUL" type instructions in agent/prompt_builder.py and leave this PR here as an option for now while I explore the prompt guidance option.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for tracing this to the Windows Git Bash execution boundary; live main still sends LocalEnvironment commands to Git Bash without a NUL normalization (tools/environments/local.py:1167-1182), while the existing prompt already states that contract (agent/prompt_builder.py:928-935).

Problems

  • tools/environments/local.py:61 treats backticks like quotes and masks their entire contents. Backticks execute command substitutions in Bash, so a nested git show missing 2>NUL || true remains unnormalized and can retain the reported failure mode. The added test at tests/tools/test_local_env_windows_msys.py:66-73 currently asserts that this class of executable text stays unchanged.
  • The global regex pass at tools/environments/local.py:85-88 has no heredoc handling. A literal heredoc payload line containing 2>NUL is rewritten even though it is data, not a redirect.

Suggested changes

  • Handle executable backtick substitutions and add a regression that verifies their nested redirect is normalized.
  • Make the scanner heredoc-aware, or narrow its scope to syntax it can distinguish safely; add quoted and unquoted heredoc payload regressions.

Automated hermes-sweeper review.

Comment thread tools/environments/local.py Outdated
Comment thread tools/environments/local.py Outdated
@helix4u

helix4u commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Closing this out. I'm doing some longer term guidance investigation. The prompt builder with more explicit instructions is probably the right direction.

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

Labels

backend/local Local shell execution P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants