From 443faa53b6c9a651b856bbfd1846fa092918a06a Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:44:52 +0100 Subject: [PATCH] Keep macOS native utilities unchanged around timeout bootstrap Expose only the installed GNU gtimeout as timeout through a new runner-owned directory. Keep native BSD utilities on their original PATH so installing the missing dependency cannot silently change the other platform checks. The connected workflow test verifies the symlink identity and that the directory contains no other utilities. Contract-Neutral: Narrow the native CI dependency PATH and its proof; no shipped installer or runtime contract changes. Change-source: pulse-maintainer --- .github/workflows/unified-agent-native.yml | 10 +++++--- .../native_guidance_tools_test.go | 25 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.github/workflows/unified-agent-native.yml b/.github/workflows/unified-agent-native.yml index 94ae17e0af..ec5fa82dc2 100644 --- a/.github/workflows/unified-agent-native.yml +++ b/.github/workflows/unified-agent-native.yml @@ -90,10 +90,12 @@ jobs: run: | set -euo pipefail brew install coreutils - coreutils_bin="$(brew --prefix coreutils)/libexec/gnubin" - test -x "$coreutils_bin/timeout" - "$coreutils_bin/timeout" --version | grep -q 'GNU coreutils' - printf '%s\n' "$coreutils_bin" >> "$GITHUB_PATH" + gnu_timeout="$(brew --prefix coreutils)/bin/gtimeout" + test -x "$gnu_timeout" + "$gnu_timeout" --version | grep -q 'GNU coreutils' + timeout_bin="$(mktemp -d "$RUNNER_TEMP/pulse-guidance-tools.XXXXXX")" + ln -s "$gnu_timeout" "$timeout_bin/timeout" + printf '%s\n' "$timeout_bin" >> "$GITHUB_PATH" - name: Verify native Unix timeout prerequisite if: matrix.unix diff --git a/scripts/installtests/native_guidance_tools_test.go b/scripts/installtests/native_guidance_tools_test.go index 2ba6999c58..c136e70d7f 100644 --- a/scripts/installtests/native_guidance_tools_test.go +++ b/scripts/installtests/native_guidance_tools_test.go @@ -65,8 +65,8 @@ func TestNativeGuidanceToolsSupplyAndVerifyRealGNUTimeout(t *testing.T) { t.Run(scenario, func(t *testing.T) { root := t.TempDir() bin, prefix := filepath.Join(root, "bin"), filepath.Join(root, "coreutils with spaces") - gnuBin := filepath.Join(prefix, "libexec", "gnubin") - for _, dir := range []string{bin, gnuBin} { + coreutilsBin, runnerTemp := filepath.Join(prefix, "bin"), filepath.Join(root, "runner temp") + for _, dir := range []string{bin, coreutilsBin, runnerTemp} { if err := os.MkdirAll(dir, 0700); err != nil { t.Fatal(err) } @@ -84,7 +84,7 @@ esac if err := os.WriteFile(filepath.Join(bin, "brew"), []byte(brew), 0700); err != nil { t.Fatal(err) } - tool := filepath.Join(gnuBin, "timeout") + tool := filepath.Join(coreutilsBin, "gtimeout") switch scenario { case "real GNU tool", "install failure": if err := os.Symlink(realTimeout, tool); err != nil { @@ -101,7 +101,7 @@ esac installExit = "23" } env := append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), "GITHUB_PATH="+pathFile, - "COREUTILS_PREFIX="+prefix, "BREW_CALLS="+callsFile, "BREW_EXIT="+installExit) + "COREUTILS_PREFIX="+prefix, "BREW_CALLS="+callsFile, "BREW_EXIT="+installExit, "RUNNER_TEMP="+runnerTemp) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "bash", "-c", bootstrap.Run) @@ -128,15 +128,26 @@ esac t.Fatalf("GNU prerequisite failed: %v: %s", runErr, output) } path, err := os.ReadFile(pathFile) - if err != nil || string(path) != gnuBin+"\n" { - t.Fatal("bootstrap did not export the exact installed gnubin path") + if err != nil { + t.Fatal(err) + } + timeoutBin := strings.TrimSuffix(string(path), "\n") + if filepath.Dir(timeoutBin) != runnerTemp || !strings.HasPrefix(filepath.Base(timeoutBin), "pulse-guidance-tools.") { + t.Fatal("bootstrap did not export its newly owned timeout directory") + } + entries, err := os.ReadDir(timeoutBin) + if err != nil || len(entries) != 1 || entries[0].Name() != "timeout" { + t.Fatal("bootstrap must not replace native macOS utilities other than timeout") + } + if target, err := os.Readlink(filepath.Join(timeoutBin, "timeout")); err != nil || target != tool { + t.Fatal("timeout must resolve to the exact installed GNU executable") } calls, err := os.ReadFile(callsFile) if err != nil || string(calls) != "install coreutils\n--prefix coreutils\n" { t.Fatal("bootstrap changed its fixed Homebrew dependency") } cmd = exec.CommandContext(ctx, "bash", "-c", verify.Run) - cmd.Env = append(env, "PATH="+strings.TrimSpace(string(path))+":"+os.Getenv("PATH")) + cmd.Env = append(env, "PATH="+timeoutBin+":"+os.Getenv("PATH")) if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("next workflow step cannot use supplied GNU timeout: %v: %s", err, output) }