Skip to content

fix(process): Program.Find used a SECOND, unfixed copy of lookPath - #10

Merged
Snider merged 1 commit into
mainfrom
lane/win-lookpath-second-copy
Aug 8, 2026
Merged

fix(process): Program.Find used a SECOND, unfixed copy of lookPath#10
Snider merged 1 commit into
mainfrom
lane/win-lookpath-second-copy

Conversation

@Snider

@Snider Snider commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

v0.16.2 fixed executable resolution on Windows and did not fix executable resolution on Windows, because this module carried two copies of it:

file state
exec/exec.go lookPath fixed in v0.16.2
os_exec_link.go lookPath byte-identical, untouched

Program.Find calls the second. os_exec_link.go:45 still read return info.Mode()&0111 != 0 — the exact defect v0.16.2's own commit message spends three paragraphs on. Every consumer resolving through Program.Find kept failing with Program.Find: "git": not found in PATH, on a release that claimed the opposite.

go-inference's windows lane measured it: 41 occurrences before v0.16.2, 41 after.

A fix is not landed until you have grepped for its siblings. Byte-identical duplicates are how a fixed defect stays live.

The fix is not a second copy of the fix

internal/lookpath now holds one implementation and both consumers call it — because the duplication is the defect's cause, and patching in place would guarantee a third divergence.

The three Windows defects it carries are unchanged from v0.16.2: no %PATHEXT% expansion, a mode&0111 test the platform can never satisfy, and a path-vs-name check that missed /.

commandContext no longer discards the failure either

It records it on Cmd.Err, exactly as exec.Command does with its own LookPath error — Start returns it without running anything.

Deferring to Start rather than returning early is deliberate. An early return skipped Service.start's exited-event broadcast, which TestService_Actions/broadcasts_exited_event_on_start_failure caught. The callers' failure handling is untouched.

Receipts — macOS, GOWORK=off (what CI runs)

go test -count=1 ./...
ok  process 13.478s · exec 0.733s · internal/lookpath 0.499s · pkg/api 2.800s

golangci-lint run ./...    0 issues
gofmt -l · go vet          clean

The regression pin

TestProgram_Find_UsesSharedResolution pins the miss itself: Program.Find must land on the same path the shared resolver reports. It fails the moment a third copy appears or the two drift apart — which a version bump could not detect, and did not.

The resolution tests move with the code into internal/lookpath, keeping the fixture-PATH + fake-%PATHEXT% receipts that prove the Windows rules on a POSIX runner.

Summary by CodeRabbit

  • Bug Fixes

    • Improved executable discovery for commands invoked by the application.
    • Added more consistent handling of PATH searches, direct paths, directory separators and Windows executable extensions.
    • Commands that cannot be resolved now report clearer failures without attempting to start.
    • Prevented directories and non-executable files from being treated as runnable programs.
  • Tests

    • Added comprehensive coverage for executable lookup and platform-specific resolution behaviour.

v0.16.2 fixed executable resolution on Windows and did not fix executable
resolution on Windows, because this module carried TWO copies of it:

  exec/exec.go        lookPath  <- fixed in v0.16.2
  os_exec_link.go     lookPath  <- byte-identical, untouched

Program.Find calls the second. os_exec_link.go:45 still read
`return info.Mode()&0111 != 0` — the exact defect v0.16.2's own commit message
spends three paragraphs on — so every consumer resolving through Program.Find
kept failing with `Program.Find: "git": not found in PATH`, on a release that
claimed the opposite. go-inference's windows lane measured it: 41 occurrences
before v0.16.2, 41 after.

A fix is not landed until you have grepped for its siblings. Byte-identical
duplicates are how a fixed defect stays live.

So the fix is not a second copy of the fix. internal/lookpath now holds ONE
implementation and both consumers call it — because the duplication IS the
defect's cause, and patching in place would guarantee a third divergence. The
three Windows defects it carries are unchanged from v0.16.2: no %PATHEXT%
expansion, a mode&0111 test the platform can never satisfy, and a path-vs-name
check that missed '/'.

commandContext no longer discards the resolution failure either. It records it
on Cmd.Err exactly as exec.Command does with its own LookPath error — Start
returns it without running anything. Deferring to Start rather than returning
early is deliberate: an early return skipped Service.start's exited-event
broadcast, which TestService_Actions/broadcasts_exited_event_on_start_failure
caught. The callers' failure handling is untouched.

Receipts — macOS, GOWORK=off (what CI runs):
  go test -count=1 ./...
  ok  process 13.478s · exec 0.733s · internal/lookpath 0.499s · pkg/api 2.800s
  golangci-lint run ./...   0 issues
  gofmt -l · go vet: clean

TestProgram_Find_UsesSharedResolution pins the miss itself: Program.Find must
land on the same path the shared resolver reports. It fails the moment a third
copy appears or the two drift apart again — which a version bump could not
detect and did not.

The resolution tests move with the code into internal/lookpath, keeping the
fixture-PATH + fake-%PATHEXT% receipts that prove the Windows rules on a POSIX
runner.

Co-Authored-By: Virgil <virgil@lethean.io>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a shared lookpath package for executable resolution. commandContext and Program.Find now use it. Platform extension handling and executable validation moved into the shared package, with new comprehensive tests.

Changes

Executable resolution

Layer / File(s) Summary
Shared resolver and validation
go/internal/lookpath/lookpath.go, go/internal/lookpath/lookpath_test.go, go/exec/exec_internal_test.go
The new package resolves direct paths and PATH entries, handles Windows extensions, detects separators, and validates executable files. Tests cover success, failure, ordering, case handling, directories, and POSIX mode bits. The previous local extension tests were removed.
Command execution integration
go/exec/exec.go, go/os_exec_link.go
commandContext uses lookpath.Look. Lookup failures are stored in Cmd.Err, while Cmd.Path retains the requested name. Successful lookups return the resolved path.
Program lookup integration
go/program.go, go/program_test.go
Program.Find uses lookpath.Look. The regression test checks unresolved names and compares successful resolution with the shared resolver.

Sequence Diagram(s)

sequenceDiagram
  participant commandContext
  participant lookpath.Look
  participant PATHAndFilesystem
  commandContext->>lookpath.Look: resolve executable
  lookpath.Look->>PATHAndFilesystem: inspect PATH and candidates
  PATHAndFilesystem-->>lookpath.Look: return path or lookup error
  lookpath.Look-->>commandContext: return core.Result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: fixing Program.Find by removing its duplicate, outdated lookPath implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.59155% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
go/internal/lookpath/lookpath.go 98.57% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Snider
Snider merged commit 1ca6201 into main Aug 8, 2026
4 of 5 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
go/internal/lookpath/lookpath.go (3)

149-151: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a doc comment to IsExecutable.

Every other exported symbol in this file is documented. IsExecutable is not. One line stating that it applies the platform extension list keeps the package surface consistent.

🤖 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 `@go/internal/lookpath/lookpath.go` around lines 149 - 151, Add a concise Go
doc comment immediately before the exported IsExecutable function, stating that
it checks executability using the platform extension list returned by
Extensions().

92-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Normalise the supplied extensions, not only base.

HasExtension lowers base but compares against extensions as given. ParsePathExt returns lower-cased entries, so the internal call paths are correct. An exported caller that passes ".EXE" gets no match, and Candidates then produces git.EXE.EXE. Lower each extension before the comparison to make the exported contract case-insensitive on both sides.

♻️ Proposed refactor
 func HasExtension(base string, extensions []string) bool {
 	lowered := core.Lower(base)
 	for _, extension := range extensions {
-		if core.HasSuffix(lowered, extension) {
+		if core.HasSuffix(lowered, core.Lower(extension)) {
 			return true
 		}
 	}
 	return false
 }
🤖 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 `@go/internal/lookpath/lookpath.go` around lines 92 - 100, Update HasExtension
to normalize each supplied extension with core.Lower before comparing it to the
already-lowered base, ensuring exported callers get case-insensitive matching on
both sides and preventing duplicate extensions such as git.EXE.EXE.

104-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Check the operating system instead of core.PathSeparator.

core.PathSeparator is only available here because it is used internally by path.Join/Join; it does not expose \\ as a stable Windows-OS constant. Use runtime.GOOS == "windows" for the Windows extension policy switch, then stop.

🤖 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 `@go/internal/lookpath/lookpath.go` around lines 104 - 109, Update Extensions
to select the Windows extension policy using runtime.GOOS == "windows" instead
of comparing core.PathSeparator, preserving nil for non-Windows systems and
ParsePathExt(core.Getenv("PATHEXT")) for Windows.

Source: Coding guidelines

go/program.go (1)

51-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Chain the resolver error into the Program.Find failure.

The failure branch discards the message that lookpath.Look produced. Look distinguishes a path-qualified target ("executable file %q not found") from a PATH search ("executable file %q not found in PATH"). Program.Find reports "not found in PATH" for both, so a Path that exists but is not runnable is described incorrectly. Keep ErrProgramNotFound as the sentinel and add the resolver detail to the message.

🤖 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 `@go/program.go` around lines 51 - 54, Update the failure branch in
Program.Find to include result’s resolver error/message in the core.E call while
retaining ErrProgramNotFound as the sentinel. Preserve lookpath.Look’s
distinction between path-qualified and PATH-search failures instead of always
reporting “not found in PATH.”
go/internal/lookpath/lookpath_test.go (1)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the tests after the symbols they now exercise.

The names carry the TestExecInternal_ prefix and the old unexported spellings, such as lookPath, isExecutable, and parsePathExt. Those symbols no longer exist. The package now exports Look, IsExecutable, ParsePathExt, Candidates, HasExtension, and ContainsSeparator. Rename to TestLook_Good, TestIsExecutable_Bad, TestParsePathExt_Ugly, and so on, so a reader can map each test to its symbol.

Also applies to: 31-31, 43-43, 57-57, 63-63, 70-70, 87-87, 98-98, 106-106, 115-115, 122-122, 129-129, 148-148, 157-157, 166-166, 177-177

🤖 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 `@go/internal/lookpath/lookpath_test.go` at line 11, Rename all tests in the
lookpath test file to match the exported symbols they exercise, removing the
TestExecInternal_ prefix and old unexported spellings. Use names such as
TestLook_Good, TestIsExecutable_Bad, TestParsePathExt_Ugly, and corresponding
names for Candidates, HasExtension, and ContainsSeparator while preserving each
test’s existing scenario suffix.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@go/internal/lookpath/lookpath_test.go`:
- Around line 297-320: The new tests must use testify assertions consistently:
in go/internal/lookpath/lookpath_test.go lines 297-320, remove assertExtensions,
replace its call sites with require.Equal(t, want, got), convert writeFixture
setup checks to require.True, and update the remaining raw assertions throughout
the file; in go/program_test.go lines 320-343, convert
TestProgram_Find_UsesSharedResolution’s four checks to require.False,
assert.Contains, require.True, and assert.Equal respectively.
- Line 1: Correct the SPDX header spelling at the top of lookpath_test.go from
“SPDX-Licence-Identifier” to the exact registered “SPDX-License-Identifier” tag,
preserving the existing license identifier.

In `@go/os_exec_link.go`:
- Around line 24-25: Rename commandContext to reflect that it does not propagate
ctx, and update all call sites accordingly; alternatively, use an existing
context-aware *core.Cmd constructor if one is available. Do not imply
cancellation support that the current core.Cmd literal cannot provide.

---

Nitpick comments:
In `@go/internal/lookpath/lookpath_test.go`:
- Line 11: Rename all tests in the lookpath test file to match the exported
symbols they exercise, removing the TestExecInternal_ prefix and old unexported
spellings. Use names such as TestLook_Good, TestIsExecutable_Bad,
TestParsePathExt_Ugly, and corresponding names for Candidates, HasExtension, and
ContainsSeparator while preserving each test’s existing scenario suffix.

In `@go/internal/lookpath/lookpath.go`:
- Around line 149-151: Add a concise Go doc comment immediately before the
exported IsExecutable function, stating that it checks executability using the
platform extension list returned by Extensions().
- Around line 92-100: Update HasExtension to normalize each supplied extension
with core.Lower before comparing it to the already-lowered base, ensuring
exported callers get case-insensitive matching on both sides and preventing
duplicate extensions such as git.EXE.EXE.
- Around line 104-109: Update Extensions to select the Windows extension policy
using runtime.GOOS == "windows" instead of comparing core.PathSeparator,
preserving nil for non-Windows systems and ParsePathExt(core.Getenv("PATHEXT"))
for Windows.

In `@go/program.go`:
- Around line 51-54: Update the failure branch in Program.Find to include
result’s resolver error/message in the core.E call while retaining
ErrProgramNotFound as the sentinel. Preserve lookpath.Look’s distinction between
path-qualified and PATH-search failures instead of always reporting “not found
in PATH.”
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b74f4bc-ec6f-4ca3-95df-e4878639d962

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0532a and 8d519d1.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • go/exec/exec.go
  • go/exec/exec_internal_test.go
  • go/internal/lookpath/lookpath.go
  • go/internal/lookpath/lookpath_test.go
  • go/os_exec_link.go
  • go/program.go
  • go/program_test.go
💤 Files with no reviewable changes (1)
  • go/exec/exec_internal_test.go

@@ -0,0 +1,320 @@
// SPDX-Licence-Identifier: EUPL-1.2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the SPDX tag spelling.

The registered tag is SPDX-License-Identifier, with the American spelling fixed by the specification. Licence scanners match that exact string, so SPDX-Licence-Identifier leaves the file unattributed.

🐛 Proposed fix
-// SPDX-Licence-Identifier: EUPL-1.2
+// SPDX-License-Identifier: EUPL-1.2
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// SPDX-Licence-Identifier: EUPL-1.2
// SPDX-License-Identifier: EUPL-1.2
🤖 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 `@go/internal/lookpath/lookpath_test.go` at line 1, Correct the SPDX header
spelling at the top of lookpath_test.go from “SPDX-Licence-Identifier” to the
exact registered “SPDX-License-Identifier” tag, preserving the existing license
identifier.

Comment on lines +297 to +320
func assertExtensions(t *testing.T, got, want []string) {
t.Helper()
if len(got) != len(want) {
t.Fatalf("got %v (%d entries), want %v (%d entries)", got, len(got), want, len(want))
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("entry %d: got %q, want %q (full: %v vs %v)", i, got[i], want[i], got, want)
}
}
}

// writeFixture creates a file with the given mode, failing the test if it
// cannot. WriteFile does not apply the mode to an existing file, so each
// fixture name is written once per test.
func writeFixture(t *testing.T, path string, mode core.FileMode) {
t.Helper()
if w := core.WriteFile(path, []byte("fixture"), mode); !w.OK {
t.Fatalf("write %s: %v", path, w.Error())
}
if c := core.Chmod(path, mode); !c.OK {
t.Fatalf("chmod %s: %v", path, c.Error())
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

New tests do not use testify. Both new test files assert with raw if statements and t.Fatal/t.Fatalf, and one adds a hand-written slice comparator. The coding guidelines require testify require for setup assertions and assert for checks in go/**/*_test.go.

  • go/internal/lookpath/lookpath_test.go#L297-L320: delete assertExtensions in favour of require.Equal(t, want, got), and convert the writeFixture result checks to require.True. Apply the same conversion to the assertions throughout the file.
  • go/program_test.go#L320-L343: convert the four checks in TestProgram_Find_UsesSharedResolution to require.False, assert.Contains, require.True, and assert.Equal.
    As per coding guidelines: "Use testify require for test setup assertions and assert for checks in test files".
📍 Affects 2 files
  • go/internal/lookpath/lookpath_test.go#L297-L320 (this comment)
  • go/program_test.go#L320-L343
🤖 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 `@go/internal/lookpath/lookpath_test.go` around lines 297 - 320, The new tests
must use testify assertions consistently: in
go/internal/lookpath/lookpath_test.go lines 297-320, remove assertExtensions,
replace its call sites with require.Equal(t, want, got), convert writeFixture
setup checks to require.True, and update the remaining raw assertions throughout
the file; in go/program_test.go lines 320-343, convert
TestProgram_Find_UsesSharedResolution’s four checks to require.False,
assert.Contains, require.True, and assert.Equal respectively.

Source: Coding guidelines

Comment thread go/os_exec_link.go
Comment on lines 24 to +25
func commandContext(ctx context.Context, name string, arg ...string) *core.Cmd {
path := name
if result := lookPath(name); result.OK {
path = result.Value.(string)
}

cmd := &core.Cmd{
Path: path,
Args: append([]string{name}, arg...),
}
return cmd
}

func lookPath(file string) core.Result {
if file == "" {
return core.Fail(core.E("lookPath", "executable file not found in PATH", nil))
}
if core.Contains(file, string(core.PathSeparator)) {
if isExecutable(file) {
return core.Ok(file)
}
return core.Fail(core.E("lookPath", core.Sprintf("executable file %q not found", file), nil))
}

for _, dir := range core.Split(core.Getenv("PATH"), string(core.PathListSeparator)) {
if dir == "" {
dir = "."
cmd := &core.Cmd{Args: append([]string{name}, arg...)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Does core expose a context-aware command constructor or cancellation fields?
rg -n --no-heading -C4 'CommandContext|type Cmd struct|Cancel\s|WaitDelay' \
  "$(go env GOMODCACHE 2>/dev/null || echo ~/go/pkg/mod)" 2>/dev/null | head -40
# How do callers use commandContext and the returned handle?
rg -n --no-heading -C6 '\bcommandContext\(' go

Repository: dAppCore/go-process

Length of output: 11472


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== os_exec_link.go =="
cat -n go/os_exec_link.go

echo
echo "== exec.Cmd fields =="
rg -n --no-heading -C8 'type Cmd struct|ctx\s+context\.' go/exec go 2>/dev/null | sed -n '1,220p'

echo
echo "== commandContext/use sites with core.Cmd fields =="
rg -n --no-heading -C12 '\bctx\b|\.Start\(|\.Wait\(|\.Kill\(|\.Process\b|cancel\(' go os_exec_link.go go/service.go go/program.go go/exec 2>/dev/null | sed -n '1,260p'

Repository: dAppCore/go-process

Length of output: 22763


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== go.mod =="
cat -n go.mod

echo
echo "== dappco.re/go Cmd/Context APIs from module cache =="
MODCACHE="$(cd go && go mod download -json dappco.re/go 2>/dev/null | jq -r '.Dir // empty' || true)"
if [ -n "${MODCACHE:-}" ]; then
  rg -n --no-heading -C8 'type Cmd struct|context\.(\w+|Context)\s+ctx|func .*CommandContext|func .*Command\(ctx|Cancel\s*func|WaitDelay|Run\(ctx|Start\(ctx|Wait\(ctx' "$MODCACHE" 2>/dev/null | sed -n '1,260p'
else
  echo "dappco.re/go module directory not available"
  fd -t f '.*\.go$' "$(go env GOMODCACHE)/dappco.re/go@*" 2>/dev/null | head -20 || true
fi

echo
echo "== local process.Cmd and Process cancellation wiring =="
cat -n go/process.go | sed -n '1,260p'
rg -n --no-heading -C6 'func \(p \*Process\) (Run|RunDir|Start|Kill|killTree|Done|IsRunning|cancel\(|KillGroup)' go/process.go go/service.go go/os_exec_link.go 2>/dev/null | sed -n '1,260p'

Repository: dAppCore/go-process

Length of output: 209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== repo files matching go.mod/go.sum =="
find . -maxdepth 3 \( -name 'go.mod' -o -name 'go.sum' \) -print

echo
echo "== package files =="
find go -type f \( -path 'go/*.go' -o -path 'go/*/*.go' \) -print | sort | sed -n '1,120p'

Repository: dAppCore/go-process

Length of output: 2088


Wire the context through *core.Cmd or rename commandContext.

*core.Cmd has no exported Canceller/WaitDelay/Context fields that this built literal can set, and the returned handle is not wrapped by exec.CommandContext. A dropped context leaves Service.StartWithOptions unable to cancel an already-running process via that handle; callers must terminate the process through *Process teardown instead. Rename this helper unless a context-aware command constructor exists.

🤖 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 `@go/os_exec_link.go` around lines 24 - 25, Rename commandContext to reflect
that it does not propagate ctx, and update all call sites accordingly;
alternatively, use an existing context-aware *core.Cmd constructor if one is
available. Do not imply cancellation support that the current core.Cmd literal
cannot provide.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant