Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions cmd/preflight/execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,16 @@ func TestTCPCommand(t *testing.T) {
})

t.Run("unreachable port", func(t *testing.T) {
_, err := executeCommand("tcp", "--timeout", "100ms", "127.0.0.1:1")
// Bind then close, so the port is known to have nothing on it. Port 1
// is not safe to assume free: a process bound to the IPv6 wildcard
// accepts IPv4-mapped connections to 127.0.0.1:1, which made this pass
// in CI and fail on developer machines.
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
addr := listener.Addr().String()
require.NoError(t, listener.Close())

_, err = executeCommand("tcp", "--timeout", "100ms", addr)
assert.Error(t, err)
})
}
Expand Down Expand Up @@ -304,16 +313,26 @@ func TestHashCommand(t *testing.T) {
}

func TestGitCommand(t *testing.T) {
t.Run("clean flag", func(t *testing.T) {
_, err := executeCommand("git", "--clean")
if err != nil {
assert.Contains(t, err.Error(), "check failed")
}
// "clean flag" used to live here, asserting inside `if err != nil` with no
// else. It could not fail: a clean tree asserted nothing, and a dirty tree
// only matched ErrCheckFailed's text, which every failing check returns. Its
// result also depended on the developer's uncommitted files, and it printed
// their private file list into test output. pkg/gitcheck covers the logic
// against mocks, so it is gone rather than rewritten.

t.Run("unknown flag is rejected", func(t *testing.T) {
// Previously "tag flag with nonexistent tag", passing --tag. There is no
// --tag flag, so assert.Error was satisfied by cobra's parse error and
// nothing about tag matching ran. The real flag is --tag-match.
_, err := executeCommand("git", "--tag", "v999.999.999")
require.Error(t, err)
assert.Contains(t, err.Error(), "unknown flag")
})

t.Run("tag flag with nonexistent tag", func(t *testing.T) {
_, err := executeCommand("git", "--tag", "v999.999.999")
assert.Error(t, err)
t.Run("tag-match against a tag that cannot exist", func(t *testing.T) {
_, err := executeCommand("git", "--tag-match", "v999.999.999-nonexistent")
require.Error(t, err)
assert.ErrorIs(t, err, ErrCheckFailed)
Comment on lines +332 to +335

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- test context ---'
sed -n '300,345p' cmd/preflight/execute_test.go

printf '%s\n' '--- git-check implementation ---'
sed -n '110,180p' pkg/gitcheck/check.go

printf '%s\n' '--- related test helpers and tag setup ---'
rg -n -C 4 'tag-match|executeCommand|git tag|no tag matches pattern|ErrCheckFailed' cmd/preflight pkg/gitcheck

Repository: vertti/preflight

Length of output: 40168


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- command helper and git runner construction ---'
sed -n '1,55p' cmd/preflight/execute_test.go
sed -n '1,100p' cmd/preflight/cmd_git.go
rg -n -C 5 'type .*GitRunner|TagsAtHead|New.*Git|gitRunner|RunGitCheck' pkg cmd

printf '%s\n' '--- repository tag state ---'
git tag --points-at HEAD
git rev-parse --show-toplevel
git status --short --branch

printf '%s\n' '--- deterministic branch probe from the implementation ---'
python3 - <<'PY'
from pathlib import Path
source = Path("pkg/gitcheck/check.go").read_text()
empty_branch = source.index("if len(tags) == 0")
match_branch = source.index("result.Failf(\"no tag matches pattern", empty_branch)
assert empty_branch < match_branch
print("TagsAtHead() == [] reaches the no-tags return before path.Match.")
print("A non-empty tag list is required to reach the no-matching-tag return.")
PY

Repository: vertti/preflight

Length of output: 12363


Ensure the test reaches the tag-mismatch branch.

Create a temporary repository with a tag at HEAD and a different --tag-match pattern, or assert no tag matches pattern in the command output. Without a tag at HEAD, the check returns earlier with no tags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/preflight/execute_test.go` around lines 332 - 335, Update the “tag-match
against a tag that cannot exist” test around executeCommand so it creates a
temporary repository with a tag on HEAD before invoking the command, then uses a
different --tag-match pattern to exercise the tag-mismatch branch;
alternatively, assert the command output contains “no tag matches pattern” while
preserving the existing ErrCheckFailed assertion.

})

t.Run("missing argument", func(t *testing.T) {
Expand Down
28 changes: 22 additions & 6 deletions pkg/gitcheck/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ func TestRealGitRunner_TagsAtHead(t *testing.T) {
assert.NoError(t, err)
}

// git runs a command with the developer's global and system config ignored.
// Inheriting them made these tests fail on real machines: commit.gpgsign=true
// aborts the commit with exit 128, and a global core.hooksPath with a failing
// pre-commit hook does the same.
func git(t *testing.T, args ...string) {
t.Helper()
cmd := exec.Command("git", args...) //nolint:gosec // args are literals from this test file
cmd.Env = append(os.Environ(),
"GIT_CONFIG_GLOBAL=/dev/null",
"GIT_CONFIG_SYSTEM=/dev/null",
Comment on lines +48 to +49

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,20p' pkg/gitcheck/runner_test.go
rg -n --glob 'go.mod' --glob '*.yml' --glob '*.yaml' \
  '//go:build|GOOS|windows|darwin|linux|unix' . || true

Repository: vertti/preflight

Length of output: 491


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- pkg/gitcheck/runner_test.go ---'
cat -n pkg/gitcheck/runner_test.go

printf '%s\n' '--- module and nearby package files ---'
cat go.mod
git ls-files 'pkg/gitcheck/*'

printf '%s\n' '--- build constraints and platform references ---'
rg -n --glob '*.go' --glob 'go.mod' --glob '*.yml' --glob '*.yaml' \
  '//go:build|// \+build|GOOS|windows|darwin|linux|unix|os.DevNull|GIT_CONFIG_GLOBAL|GIT_CONFIG_SYSTEM|GIT_DIR|GIT_WORK_TREE|GIT_INDEX_FILE' .

Repository: vertti/preflight

Length of output: 8786


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

p = Path("pkg/gitcheck/runner_test.go")
text = p.read_text()
lines = text.splitlines()

print("package:", next((x for x in lines if x.startswith("package ")), "<none>")
print("leading build constraints:")
for line in lines[:12]:
    print(repr(line))
print("uses os.DevNull:", "os.DevNull" in text)
print("uses /dev/null:", "/dev/null" in text)
print("clears Git repository selectors:")
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"):
    print(name, any(name in line for line in lines))
PY

Repository: vertti/preflight

Length of output: 253


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- pkg/gitcheck/runner.go ---'
cat -n pkg/gitcheck/runner.go

printf '%s\n' '--- gitcheck tests and call sites ---'
cat -n pkg/gitcheck/check_test.go
rg -n 'RealGitRunner|exec.Command|cmd\.Env|GIT_' pkg/gitcheck '*.go' 2>/dev/null || true

printf '%s\n' '--- repository test/build configuration ---'
rg -n --hidden --glob '!*.sum' --glob '!vendor/**' \
  'GOOS|go test|go vet|windows|unix' .github Makefile* README.md 2>/dev/null || true

Repository: vertti/preflight

Length of output: 13198


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

p = Path("pkg/gitcheck/runner_test.go")
lines = p.read_text().splitlines()
print("leading build constraints:")
for line in lines[:12]:
    print(repr(line))
print("uses os.DevNull:", any("os.DevNull" in line for line in lines))
print("uses /dev/null:", any("/dev/null" in line for line in lines))
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"):
    print(f"{name} referenced:", any(name in line for line in lines))
PY

Repository: vertti/preflight

Length of output: 530


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read-only semantic probe for the proposed platform-neutral value.
cat > /tmp/probe.go <<'EOF'
package main

import (
	"fmt"
	"os"
	"runtime"
)

func main() {
	fmt.Printf("GOOS=%s os.DevNull=%q\n", runtime.GOOS, os.DevNull)
}
EOF

go run /tmp/probe.go

Repository: vertti/preflight

Length of output: 188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat > /tmp/probe.go <<'EOF'
package main

import (
	"fmt"
	"os"
	"runtime"
)

func main() {
	fmt.Printf("GOOS=%s os.DevNull=%q\n", runtime.GOOS, os.DevNull)
}
EOF

go run /tmp/probe.go

Repository: vertti/preflight

Length of output: 188


Use os.DevNull for both Git config paths. "/dev/null" is invalid on Windows, which is a supported build target. Keep this test cross-platform instead of adding a Unix-only build tag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/gitcheck/runner_test.go` around lines 48 - 49, Update the environment
arguments in the relevant test setup to use os.DevNull for both
GIT_CONFIG_GLOBAL and GIT_CONFIG_SYSTEM instead of the hardcoded "/dev/null"
paths, preserving cross-platform test support without adding build tags.

Source: Coding guidelines

"GIT_TERMINAL_PROMPT=0",
Comment on lines +47 to +50

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

env | rg '^GIT_(DIR|WORK_TREE|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES)=' || true
rg -n -C 3 'exec\.Command\("git"|cmd\.Env|GIT_(DIR|WORK_TREE|INDEX_FILE)' \
  pkg/gitcheck/runner_test.go pkg/gitcheck/runner.go || true

Repository: vertti/preflight

Length of output: 2213


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- runner_test.go outline ---'
ast-grep outline pkg/gitcheck/runner_test.go || true
printf '%s\n' '--- runner.go outline ---'
ast-grep outline pkg/gitcheck/runner.go || true
printf '%s\n' '--- runner_test.go relevant sections ---'
cat -n pkg/gitcheck/runner_test.go | sed -n '1,115p'
printf '%s\n' '--- runner.go relevant sections ---'
cat -n pkg/gitcheck/runner.go | sed -n '1,105p'
printf '%s\n' '--- build constraints and related environment setup ---'
rg -n -C 3 '//go:build|// \+build|t\.Setenv|os\.Setenv|GIT_(DIR|WORK_TREE|INDEX_FILE|OBJECT_DIRECTORY|ALTERNATE_OBJECT_DIRECTORIES)|Chdir|cmd\.Dir' \
  pkg/gitcheck .github 2>/dev/null || true

Repository: vertti/preflight

Length of output: 8820


🌐 Web query:

Git documentation GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE environment variables git repository selection

💡 Result:

Git uses the GIT_DIR, GIT_WORK_TREE, and GIT_INDEX_FILE environment variables to control how it locates and interacts with a repository [1][2]. GIT_DIR The GIT_DIR variable specifies the path to the repository, which is the directory containing the Git metadata (typically named.git) [3][1]. When GIT_DIR is set, Git bypasses its standard repository discovery process—which otherwise searches for a.git directory in the current working directory and its parent directories [3][1]. Setting this variable explicitly defines the base of the repository [1][4]. GIT_WORK_TREE The GIT_WORK_TREE variable specifies the root path of the working tree [1][2]. It is used in conjunction with GIT_DIR (or the --git-dir command-line option) to define where your project files are located [1][5]. If GIT_DIR is set but GIT_WORK_TREE (or the --work-tree command-line option and core.worktree configuration) is not, Git defaults to assuming that the current working directory is the root of the working tree [3][1]. GIT_INDEX_FILE The GIT_INDEX_FILE variable specifies the path to an alternate index file [1][2]. By default, Git uses the index file located at $GIT_DIR/index [1][2]. Setting this variable allows you to target a different index, which is often used in specialized operations or when manipulating multiple index states [1]. Summary of Repository Selection Under normal circumstances, Git performs repository discovery by walking up the directory tree to find a.git subdirectory, which identifies both the repository location and the top level of the working tree [3][1]. Setting GIT_DIR disables this automatic discovery [1][6]. If you provide a GIT_DIR path that is not at the top level of your project, you must also provide the correct path to the working tree via GIT_WORK_TREE to ensure Git operates on the intended files [1][7]. Command-line options like --git-dir and --work-tree serve as functional equivalents to these environment variables and take precedence over them [1][4].

Citations:


🌐 Web query:

Go os/exec Cmd.Env inherited environment when Env is nil duplicate environment variables

💡 Result:

In Go's os/exec package, the behavior of the Env field in the Cmd struct is as follows: 1. Inheritance Behavior: If the Env field is nil (its default value), the new process inherits the current process's environment [1][2]. 2. Duplicate Environment Variables: If you provide a non-nil slice to Env, and that slice contains duplicate keys (e.g., FOO=bar, FOO=baz), the system uses only the last value provided for each key [1][3]. This applies regardless of whether you are overriding the entire environment or appending to it [1][4]. 3. Combining with Current Environment: If you want to keep the current environment variables while adding or overriding specific ones, you must explicitly populate the Env slice, typically by appending to a copy of the current environment [4]: cmd:= exec.Command("prog") cmd.Env = append(os.Environ, "KEY=value") // Inherits current environment plus new/overridden values Because Go deduplicates these variables by keeping the last occurrence, placing your new or overridden values at the end of the slice ensures they take precedence over any inherited values [1][3]. Note: On Windows, there is a special case where the SYSTEMROOT variable is automatically added if it is missing and not explicitly set to an empty string [1][2]. Additionally, the Dir field may also influence the PWD environment variable on Unix systems [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import os
import subprocess
from pathlib import Path

source = Path("pkg/gitcheck/runner_test.go").read_text()
required = [
    '"GIT_CONFIG_GLOBAL=/dev/null"',
    '"GIT_CONFIG_SYSTEM=/dev/null"',
    '"GIT_TERMINAL_PROMPT=0"',
]
assert all(item in source for item in required)

inherited = dict(os.environ)
inherited.update({
    "GIT_DIR": "/external/repository/.git",
    "GIT_WORK_TREE": "/external/repository",
    "GIT_INDEX_FILE": "/external/repository/.git/index",
})
child_env = [f"{key}={value}" for key, value in inherited.items()]
child_env += [
    "GIT_CONFIG_GLOBAL=/dev/null",
    "GIT_CONFIG_SYSTEM=/dev/null",
    "GIT_TERMINAL_PROMPT=0",
]

result = subprocess.run(
    ["/usr/bin/env"],
    env=dict(item.split("=", 1) for item in child_env),
    text=True,
    capture_output=True,
    check=True,
)
child = dict(line.split("=", 1) for line in result.stdout.splitlines() if "=" in line)
for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE"):
    assert child[key] == inherited[key]
print("constructed child environment preserves all three repository-selection variables")

root = subprocess.check_output(
    ["git", "rev-parse", "--show-toplevel"],
    text=True,
).strip()
probe_env = dict(os.environ)
probe_env["GIT_DIR"] = str(Path(root) / ".git")
probe_env.pop("GIT_WORK_TREE", None)
probe_env.pop("GIT_INDEX_FILE", None)
selected = subprocess.check_output(
    ["git", "rev-parse", "--show-toplevel"],
    cwd="/tmp",
    env=probe_env,
    text=True,
).strip()
assert selected == root
print(f"inherited GIT_DIR selects {selected} even when git runs from /tmp")
PY

Repository: vertti/preflight

Length of output: 324


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import os
import subprocess
from pathlib import Path

root = subprocess.check_output(
    ["git", "rev-parse", "--show-toplevel"],
    text=True,
).strip()
git_dir = subprocess.check_output(
    ["git", "rev-parse", "--git-dir"],
    text=True,
).strip()
if not os.path.isabs(git_dir):
    git_dir = str(Path(root, git_dir).resolve())

probe_env = dict(os.environ)
probe_env["GIT_DIR"] = git_dir
probe_env["GIT_WORK_TREE"] = root
probe_env.pop("GIT_INDEX_FILE", None)

selected = subprocess.check_output(
    ["git", "rev-parse", "--show-toplevel"],
    cwd="/tmp",
    env=probe_env,
    text=True,
).strip()

assert selected == root, (root, selected)
print(f"GIT_DIR and GIT_WORK_TREE select {selected} when git runs from /tmp")
PY

Repository: vertti/preflight

Length of output: 230


Isolate Git repository variables in test commands.

If the test process inherits GIT_DIR, GIT_WORK_TREE, or GIT_INDEX_FILE, the commands in TestRealGitRunner_TagsAtHead_WithTag can target repository metadata, the work tree, or the index outside t.TempDir(). RealGitRunner also inherits these variables because its exec.Cmd.Env is nil. Filter them from the child environment and use the same controlled environment for RealGitRunner.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/gitcheck/runner_test.go` around lines 47 - 50, Update
TestRealGitRunner_TagsAtHead_WithTag and the RealGitRunner command setup to
build a controlled child environment from os.Environ() that excludes GIT_DIR,
GIT_WORK_TREE, and GIT_INDEX_FILE, then apply it to both test commands and
RealGitRunner’s exec.Cmd.Env while preserving the existing Git configuration
overrides.

)
out, err := cmd.CombinedOutput()
require.NoError(t, err, "git %v: %s", args, out)
}

func TestRealGitRunner_IsGitRepo_NotRepo(t *testing.T) {
tmpDir := t.TempDir()
oldWd, err := os.Getwd()
Expand All @@ -60,13 +76,13 @@ func TestRealGitRunner_TagsAtHead_WithTag(t *testing.T) {
require.NoError(t, os.Chdir(tmpDir))

// Initialize git repo with a commit and tag
require.NoError(t, exec.Command("git", "init").Run())
require.NoError(t, exec.Command("git", "config", "user.email", "test@test.com").Run())
require.NoError(t, exec.Command("git", "config", "user.name", "Test").Run())
git(t, "init")
git(t, "config", "user.email", "test@test.com")
git(t, "config", "user.name", "Test")
require.NoError(t, os.WriteFile("test.txt", []byte("test"), 0o600))
require.NoError(t, exec.Command("git", "add", "test.txt").Run())
require.NoError(t, exec.Command("git", "commit", "-m", "initial").Run())
require.NoError(t, exec.Command("git", "tag", "v1.0.0").Run())
git(t, "add", "test.txt")
git(t, "commit", "-m", "initial")
git(t, "tag", "v1.0.0")

runner := &RealGitRunner{}
tags, err := runner.TagsAtHead()
Expand Down
85 changes: 49 additions & 36 deletions pkg/preflightfile/preflightfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"path/filepath"
"reflect"
"testing"

"github.com/stretchr/testify/require"
)

func TestFindFile_ExplicitPath(t *testing.T) {
Expand Down Expand Up @@ -51,54 +53,65 @@ func TestFindFile_TraverseUp(t *testing.T) {
}
}

// A repo root is a boundary: a .preflight above it belongs to something else.
// The .preflight must live only in the parent, or FindFile returns on its first
// iteration and the .git logic is never reached — which is what the previous
// version of this test did, so it passed with that logic deleted.
func TestFindFile_StopAtGit(t *testing.T) {
tmpDir := t.TempDir()

projectDir := filepath.Join(tmpDir, "project")
gitDir := filepath.Join(projectDir, ".git")
if err := os.MkdirAll(gitDir, 0o700); err != nil {
t.Fatalf("failed to create directories: %v", err)
}
require.NoError(t, os.MkdirAll(filepath.Join(projectDir, ".git"), 0o700))
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, ".preflight"), []byte("env HOME\n"), 0o600))

preflightPath := filepath.Join(tmpDir, ".preflight")
if err := os.WriteFile(preflightPath, []byte("test"), 0o600); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
_, err := FindFile(projectDir, "")
require.Error(t, err, ".git is a boundary: the parent's .preflight is not ours")
}

projectPreflight := filepath.Join(projectDir, ".preflight")
if err := os.WriteFile(projectPreflight, []byte("test"), 0o600); err != nil {
t.Fatalf("failed to create test file: %v", err)
}
// The mirror image: without a .git boundary the parent's file is found, which
// pins that the error above comes from the boundary and not from the walk
// failing generally.
func TestFindFile_WalksUpWithoutGitBoundary(t *testing.T) {
tmpDir := t.TempDir()
projectDir := filepath.Join(tmpDir, "project")
require.NoError(t, os.MkdirAll(projectDir, 0o700))
parentPreflight := filepath.Join(tmpDir, ".preflight")
require.NoError(t, os.WriteFile(parentPreflight, []byte("env HOME\n"), 0o600))

found, err := FindFile(projectDir, "")
if err != nil {
t.Fatalf("FindFile failed: %v", err)
}
if found != projectPreflight {
t.Errorf("expected %q, got %q", projectPreflight, found)
}
require.NoError(t, err)
require.Equal(t, parentPreflight, found)
}

// The search stops at $HOME. This uses a temporary HOME rather than the real
// one: the previous version created and RemoveAll'd ~/test_preflight on the
// developer's machine, and failed outright for anyone who has a global
// ~/.preflight — which is a supported location this package exists to find.
func TestFindFile_StopAtHome(t *testing.T) {
homeDir, err := os.UserHomeDir()
if err != nil {
t.Fatalf("failed to get home directory: %v", err)
}
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home) // os.UserHomeDir on Windows

testDir := filepath.Join(homeDir, "test_preflight")
if err := os.MkdirAll(testDir, 0o700); err != nil {
t.Fatalf("failed to create test directory: %v", err)
}
defer func() {
if err := os.RemoveAll(testDir); err != nil {
t.Errorf("failed to clean up test directory: %v", err)
}
}()
testDir := filepath.Join(home, "project")
require.NoError(t, os.MkdirAll(testDir, 0o700))

_, err = FindFile(testDir, "")
if err == nil {
t.Error("expected error when .preflight not found")
}
_, err := FindFile(testDir, "")
require.Error(t, err, "no .preflight anywhere up to HOME")
}

func TestFindFile_StopsBeforeLeavingHome(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)

// A .preflight above HOME must not be picked up.
require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(home), ".preflight"), []byte("env HOME\n"), 0o600))
t.Cleanup(func() { _ = os.Remove(filepath.Join(filepath.Dir(home), ".preflight")) })
Comment on lines +102 to +108

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep the fixture inside the test-owned temporary directory.

Line 107 writes .preflight to the parent of t.TempDir(). That parent can be a shared directory such as /tmp. The test can overwrite or delete another process's .preflight file.

Create home as a child of a dedicated t.TempDir() directory. Put the parent .preflight in that dedicated directory. Then remove the manual cleanup.

Proposed fix
-func TestFindFile_StopsBeforeLeavingHome(t *testing.T) {
-	home := t.TempDir()
+func TestFindFile_StopsBeforeLeavingHome(t *testing.T) {
+	root := t.TempDir()
+	home := filepath.Join(root, "home")
+	require.NoError(t, os.MkdirAll(home, 0o700))
 	t.Setenv("HOME", home)
 	t.Setenv("USERPROFILE", home)

 	// A .preflight above HOME must not be picked up.
-	require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(home), ".preflight"), []byte("env HOME\n"), 0o600))
-	t.Cleanup(func() { _ = os.Remove(filepath.Join(filepath.Dir(home), ".preflight")) })
+	require.NoError(t, os.WriteFile(filepath.Join(root, ".preflight"), []byte("env HOME\n"), 0o600))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/preflightfile/preflightfile_test.go` around lines 102 - 108, Update the
test setup around home and the .preflight fixture so home is created as a child
of a dedicated t.TempDir() directory, with the parent .preflight written in that
test-owned directory. Remove the manual t.Cleanup removal, since the temporary
directory lifecycle handles cleanup safely.


testDir := filepath.Join(home, "project")
require.NoError(t, os.MkdirAll(testDir, 0o700))

_, err := FindFile(testDir, "")
require.Error(t, err, "search must stop at HOME, not walk past it")
}

func TestParseFile(t *testing.T) {
Expand Down