Skip to content

Commit 0c0d6d9

Browse files
committed
Fix PATH resolution aborting on first non-NotFound stat error
The resolution loops in resolveCommandPathForPlatform used Effect.mapError on isExecutableFile, which propagated PlatformErrors (e.g. PermissionDenied) and short-circuited the generator on the first failing probe. This prevented subsequent PATH entries from being checked. Extract a probeExecutable helper that uses Effect.catch to convert probe errors into false, allowing the loop to continue scanning all PATH entries and command candidates. The last encountered probe error is preserved and attached as the cause on the final CommandResolutionError when no executable is found.
1 parent c7a7117 commit 0c0d6d9

1 file changed

Lines changed: 13 additions & 12 deletions

File tree

packages/shared/src/shell.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -552,17 +552,22 @@ const resolveCommandPathForPlatform = Effect.fn("shell.resolveCommandPathForPlat
552552
path.extname,
553553
);
554554

555+
let lastProbeCause: PlatformError.PlatformError | undefined;
556+
const probeExecutable = (filePath: string) =>
557+
isExecutableFile(filePath, platform, windowsPathExtensions).pipe(
558+
Effect.catch((error) => {
559+
lastProbeCause = error;
560+
return Effect.succeed(false as const);
561+
}),
562+
);
563+
555564
if (command.includes("/") || command.includes("\\")) {
556565
for (const candidate of commandCandidates) {
557-
if (
558-
yield* isExecutableFile(candidate, platform, windowsPathExtensions).pipe(
559-
Effect.mapError((cause) => new CommandResolutionError({ command, platform, cause })),
560-
)
561-
) {
566+
if (yield* probeExecutable(candidate)) {
562567
return candidate;
563568
}
564569
}
565-
return yield* new CommandResolutionError({ command, platform });
570+
return yield* new CommandResolutionError({ command, platform, cause: lastProbeCause });
566571
}
567572

568573
const pathValue = resolvePathEnvironmentVariable(env);
@@ -580,16 +585,12 @@ const resolveCommandPathForPlatform = Effect.fn("shell.resolveCommandPathForPlat
580585
for (const pathEntry of pathEntries) {
581586
for (const candidate of commandCandidates) {
582587
const candidatePath = path.join(pathEntry, candidate);
583-
if (
584-
yield* isExecutableFile(candidatePath, platform, windowsPathExtensions).pipe(
585-
Effect.mapError((cause) => new CommandResolutionError({ command, platform, cause })),
586-
)
587-
) {
588+
if (yield* probeExecutable(candidatePath)) {
588589
return candidatePath;
589590
}
590591
}
591592
}
592-
return yield* new CommandResolutionError({ command, platform });
593+
return yield* new CommandResolutionError({ command, platform, cause: lastProbeCause });
593594
});
594595

595596
export const resolveCommandPath = Effect.fn("shell.resolveCommandPath")(function* (

0 commit comments

Comments
 (0)