[BUG] Claude Desktop (MSIX) bundled bash leaks TEMP=/tmp into child JVMs, breaking Gradle and any Java Selector.open() on Windows
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
When Claude Desktop for Windows is installed via the MSIX package, the bundled Cygwin/Git-Bash shell exposes TEMP=/tmp and TMP=/tmp to subprocesses. Any native-Windows JVM (JDK 16+ when PipeImpl uses AF_UNIX for the Selector wakeup pipe) spawned from that Bash fails at Selector.open() with Unable to establish loopback connection / UnixDomainSockets.connect0 EINVAL.
This makes ./gradlew <task> unusable from the Claude Desktop Bash tool. The Claude Code CLI is not affected — only the Desktop App's bundled shell.
Distinct from #41432: that one is about Cowork's VM altering the env of stdio MCP child processes. This one is about the MSIX-bundled shell itself leaking Cygwin-style /tmp into JVM children, independent of Cowork or MCP.
Important context: Anthropic currently does not ship an MSI installer for Claude Desktop on Windows. The only Windows distribution channel is the MSIX-based installer (the public website serves a ~6.7 MB stub that downloads the MSIX). End users cannot opt out of MSIX, which means the "distribute as MSI" workaround commonly suggested for MSIX/JVM incompatibilities (see MS Q&A 5599711) is not available to us — only Anthropic can fix this.
What Should Happen?
The bundled Bash should not propagate Cygwin-style TEMP=/tmp / TMP=/tmp to native-Windows child processes. Either:
- Scrub
TEMP/TMPbefore spawning child processes, OR - Translate them to Windows paths (e.g., the user's
%LOCALAPPDATA%\Temp), OR - Honor the
envfield in~/.claude/settings.jsonso users can scrub these themselves (currently has no effect on Bash subprocesses on MSIX installs).
./gradlew build invoked from the Claude Desktop Bash tool should succeed without manual unset TEMP TMP per call.
Error Messages/Logs
java.io.IOException: Unable to establish loopback connection
at java.base/sun.nio.ch.PipeImpl$Initializer.run
at java.base/sun.nio.ch.PipeImpl$Initializer.init (PipeImpl.java:96)
at java.base/sun.nio.ch.PipeImpl.<init> (PipeImpl.java:186)
at java.base/sun.nio.ch.WEPollSelectorImpl.<init> (WEPollSelectorImpl.java:78)
at java.base/sun.nio.ch.WEPollSelectorProvider.openSelector (WEPollSelectorProvider.java:33)
Caused by: java.net.SocketException: Invalid argument: connect
at java.base/sun.nio.ch.UnixDomainSockets.connect0 (Native Method)
Steps to Reproduce
Detection first (so you can confirm you're on the affected setup):
# 1) Bash env shows Cygwin-style /tmp leaked in?
echo "TEMP=$TEMP TMP=$TMP" # → TEMP=/tmp TMP=/tmp on affected setups
# 2) Claude Desktop is the MSIX build?
Get-Process claude | Select-Object -First 1 -ExpandProperty Path
# → contains WindowsApps\Claude_* → MSIX
Minimal reproducer (no Gradle needed):
- In the Claude Desktop Bash tool, create
SelTest.java:
``java``
import java.nio.channels.Selector;
public class SelTest {
public static void main(String[] a) throws Exception {
Selector s = Selector.open();
System.out.println("OK: " + s.getClass().getName());
s.close();
}
}
- Compile and run:
``bash``
cd /tmp && javac SelTest.java && java SelTest
# → java.io.IOException: Unable to establish loopback connection
- Run again with
TEMP/TMPscrubbed:
``bash``
cmd //c "set TEMP= && set TMP= && java SelTest"
# → OK: sun.nio.ch.WEPollSelectorImpl
Same JVM, same JAR, only difference is TEMP / TMP reaching the JVM.
Claude Model
Not sure / Multiple models
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
Claude 1.8555.2 (a476c3) 2026-05-22T23:04:37.000Z
Platform
Other
Operating System
Windows
Terminal/Shell
Other
Additional Information
Root cause
The MSIX-packaged Bash sets TEMP=/tmp and TMP=/tmp for the shell process. These propagate to every child process via the standard environment. JDK 16+ uses UnixDomainSockets in sun.nio.ch.PipeImpl for the Selector wakeup pipe on Windows (JEP 380), and the wepoll Selector that drives it became default in JDK 17 (JDK-8266369, see JDK 17 release notes). The AF_UNIX path in PipeImpl was "temporarily" disabled in JDK-8280233 and later re-enabled, so whether a given JDK 17.x build triggers depends on whether its PipeImpl currently uses AF_UNIX or TCP for the wakeup pipe. The socket path is derived from TEMP; /tmp is not a valid Windows path, so connect0 returns EINVAL. Verified on JDK 21 and JDK 25.
The Cygwin manual (Environment Variables) calls out exactly this risk:
TMPDIR, TMP, TEMP are converted to UNIX format … will be used by some Cygwin applications, possibly with unexpected results.
Microsoft staff have confirmed on MS Q&A 5599711 that MSIX AppContainer does not formally support Java NIO selectors / Unix-domain sockets — though that thread is about JVM apps packaged as MSIX, not env-leak to child JVMs.
Per-call workaround
unset TEMP TMP; ./gradlew <task>
What does NOT work
- Setting
"env": { "TEMP": "...", "TMP": "..." }in~/.claude/settings.json— MSIX still overrides or theenvfield doesn't reach Bash subprocesses. -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.WindowsSelectorProvider— JDK 21+ ignores it; the legacy provider has been removed.- Setting
javaToolchaininbuild.gradle.ktsto a JDK without AF_UNIX inPipeImpl(e.g., JDK 11). The JVM that fails theSelector.open()is the Gradle daemon JVM (org.gradle.java.home/JAVA_HOME), not the project's toolchain. The daemon forks tasks; toolchain only affects compile/test JVMs invoked by the daemon.
Secondary problem: Gradle daemon poisoning
unset TEMP TMP only fixes the current shell. If the first ./gradlew that spawns a daemon JVM has the leaked env, the daemon (3-hour idleTimeout) captures TMP=/tmp and holds it. Every subsequent caller — including plain .bat from Explorer with clean Windows env — attaches to the poisoned daemon, and its forked tasks (:run, test runner) inherit the bad TMP.
Verify by reading ~/.gradle/daemon/<version>/daemon-<pid>.out.log and looking for the env dump line:
EstablishBuildEnvironment Configuring env variables: [..., MSYSTEM=…, CLAUDECODE=…]
Presence of MSYSTEM / CLAUDECODE ⇒ daemon spawned from the Claude Desktop shell ⇒ poisoned.
Fix:
./gradlew --stop # or, if hung: taskkill /F /IM java.exe
unset TEMP TMP
./gradlew <task> # daemon respawns with clean env
Asks
- Fix in Claude Desktop: make the bundled Bash not leak Cygwin-style
TEMP/TMPto child processes, or at minimum document the gotcha for users running JVM tooling. (An MSI installer is not offered, so this cannot be worked around by switching package format.) - If the
envfield insettings.jsonis intended to scrub subprocess env, fix it — currently it has no effect on Bash subprocess env on MSIX installs. - If this repo is the wrong place to report Claude Desktop bugs, please point to the right channel.
Related
- anthropics/claude-code#41432 — same
WEPollSelectorImplsymptom on Claude Desktop Windows, different trigger (Cowork VM altering child env). - gradle/gradle#2243 — generic Gradle "loopback connection" reports.
- sandboxie-plus/Sandboxie#1009 — confirms sandboxing in general can break Gradle the same way.