Repository navigation
git on windows-11-arm rejects NUL as a config path: os.DevNull is a valid write target but not a valid GIT_CONFIG_* value #684
Description
Feature: #91 — Windows support audit
Fixed in #682 (commit 9ad98fa1). Filed for the record, because the finding outlives the fix
and the failure mode is not one you would predict from reading the call site.
What happened
The first CI run that included windows-11-arm failed both of its legs. Four tests, one cause:
git [init --quiet --initial-branch=main]: exit status 128
fatal: unable to access 'NUL': Invalid argument
TestRepo_Add_ClonesURL_DefaultHomeTestRepo_Add_ClonesURL_PositionalDestinationTestWritDeployScenario_HarnessTestWritDeployScenario_Deploy
Two test helpers isolated git from the developer's configuration like this:
cmd.Env = append(os.Environ(), "GIT_CONFIG_GLOBAL="+os.DevNull, "GIT_CONFIG_SYSTEM="+os.DevNull)os.DevNull is "NUL" on Windows. The git build on the windows-11-arm runner image refuses to
open it as a config path. The identical command succeeds on windows-latest, so this is a
property of that image's git, not of the architecture — and it is why the defect stayed invisible
until windows/arm64 joined the matrix.
The distinction worth remembering
os.DevNull is correct as a write target on Windows. devlore-test uses it that way in ~20
places for discard destinations, and cmd/devlore-test/devloretest/commands.go already says so:
"The discard destination needs no sentinel: os.DevNull already names the platform's own." Those
uses are fine and were deliberately left alone.
The failure is narrower: git opening the null device as a config file to read. At the call
site the two uses look identical, which is what makes this worth writing down.
The fix
An empty regular file. It is what the helpers actually wanted — a config git can open and find
nothing in — and it carries none of the device semantics that vary by platform and by git build.
func isolatedGitEnv(t *testing.T) []string {
t.Helper()
empty := filepath.Join(t.TempDir(), "gitconfig")
if err := os.WriteFile(empty, nil, 0o600); err != nil {
t.Fatalf("write empty git config: %v", err)
}
return append(os.Environ(), "GIT_CONFIG_GLOBAL="+empty, "GIT_CONFIG_SYSTEM="+empty)
}Rejected alternatives:
- A nonexistent path works today, but depends on how each git version treats an unreadable
config rather than on documented behavior. git -c key=valueoverrides individual keys without preventing the global config from being
read at all — whack-a-mole againstcore.hooksPathand anything else a developer has set.- Setting
HOMEcovers global config but notGIT_CONFIG_SYSTEM.
Audit
Both affected sites are fixed; there is no third. Across the module:
GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEMare set in exactly those two helpers.- The one production consumer,
pkg/gitignore/tracker.go:261, readsGIT_CONFIG_GLOBALand
continues past a path it cannot open — so a user whose environment carries
GIT_CONFIG_GLOBAL=NULdegrades quietly rather than failing.
Why it is worth a record
The guidance — never hand git a device path as a config file; hand it an empty file — lives only
in the doc comments of the two helpers. Someone reaching for os.DevNull in a different package,
for a different tool, will not be reading those.
Related: #681 (added the windows/arm64 leg that surfaced this), #671 (running the execution matrix
on real machines — this is that class of discovery), #599 (Windows/ARM64 scenario work).
Activity