Skip to content

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

@David-Noble-at-work

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_DefaultHome
  • TestRepo_Add_ClonesURL_PositionalDestination
  • TestWritDeployScenario_Harness
  • TestWritDeployScenario_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=value overrides individual keys without preventing the global config from being
    read at all — whack-a-mole against core.hooksPath and anything else a developer has set.
  • Setting HOME covers global config but not GIT_CONFIG_SYSTEM.

Audit

Both affected sites are fixed; there is no third. Across the module:

  • GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM are set in exactly those two helpers.
  • The one production consumer, pkg/gitignore/tracker.go:261, reads GIT_CONFIG_GLOBAL and
    continues past a path it cannot open — so a user whose environment carries
    GIT_CONFIG_GLOBAL=NUL degrades 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).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area:TestingTest coverage and verificationEpic:WindowsCampaignThe Windows campaign epicbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions