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
25 changes: 22 additions & 3 deletions skills/pragmatic-orchestration/scripts/lib/backend_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,28 @@ if [[ -z "$BIN" ]]; then
echo "Error: unknown backend '$BACKEND' for agent $AGENT_ID" >&2
exit $EXIT_CONFIG_ERROR
fi
if ! command -v "$BIN" &>/dev/null && [[ ! -x "$BIN" ]]; then
echo "Error: backend CLI not found: $BIN (backend=$BACKEND)" >&2
exit $EXIT_CONFIG_ERROR
bin_usable() {
command -v "$1" &>/dev/null || [[ -x "$1" ]]
}
if ! bin_usable "$BIN"; then
# Native Windows Python can resolve an npm .CMD shim that MSYS bash cannot
# execute. Use its extensionless sibling, keeping the resolved directory
# (and any explicit binary override) authoritative.
_bin_fallback=""
if [[ "$BIN" == *[/\\]* && "$BIN" == *.[cC][mM][dD] ]]; then
_bin_fallback="${BIN%.*}"
_bin_fallback="${_bin_fallback//\\//}"
if command -v cygpath &>/dev/null; then
_bin_fallback="$(cygpath -u "$_bin_fallback" 2>/dev/null)" || _bin_fallback=""
fi
fi
if [[ -n "$_bin_fallback" && -x "$_bin_fallback" ]]; then
BIN="$_bin_fallback"
else
echo "Error: backend CLI not found: $BIN (backend=$BACKEND)" >&2
exit $EXIT_CONFIG_ERROR
fi
unset _bin_fallback
fi

# Codex model alias + default efforts are already applied by backend_contract
Expand Down
59 changes: 59 additions & 0 deletions skills/pragmatic-orchestration/scripts/tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,65 @@ xml=$(CLAUDE_MODEL="claude-runtime" CLAUDE_EFFORT="max" \
assert_contains "ask XML uses resolved model" "$xml" 'model="claude-runtime"'
assert_contains "ask XML uses resolved effort" "$xml" 'effort="max"'

echo "=== Windows .cmd shim binary resolution ==="
# On Windows the contract resolves binaries with Python's shutil.which, which
# walks PATHEXT and returns the `foo.CMD` sibling spelled with backslashes. npm
# writes that shim without the executable bit, so bash can neither `command -v`
# the path nor exec-test it, and every npm-installed backend was rejected with a
# false "backend CLI not found". Reproduced here on any platform by pointing the
# override at such a path; the extensionless launcher is the usable one.
SHIM_DIR="$TMP/cmd-shim"
mkdir -p "$SHIM_DIR"
cp "$FAKES/fake-codex" "$SHIM_DIR/shimcodex"
chmod +x "$SHIM_DIR/shimcodex"
printf '@echo off\r\n' > "$SHIM_DIR/shimcodex.cmd"
chmod 644 "$SHIM_DIR/shimcodex.cmd"

export PORCH_RUN_DIR="$TMP/run-ask-cmd-shim"
mkdir -p "$PORCH_RUN_DIR"
set +e
out=$(PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="$SHIM_DIR\\shimcodex.CMD" \
"$PORCH" review ask --progress none -a codex "review this" \
2>"$TMP/ask-cmd-shim.err")
set -e
assert_contains "windows .cmd path falls back to extensionless launcher" "$out" "FAKE_CODEX_OK"
assert_not_contains "windows .cmd path is not reported missing" \
"$(cat "$TMP/ask-cmd-shim.err")" "backend CLI not found"

# A bare name that resolves to nothing must still fail; the fallback only
# applies to values that look like a resolved path.
set +e
PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="definitely-absent-cli" \
"$PORCH" review ask --progress none -a codex "review this" \
>"$TMP/ask-absent-bin.out" 2>"$TMP/ask-absent-bin.err"
absent_bin_rc=$?
set -e
assert_eq "missing bare binary still fails" "$absent_bin_rc" "3"

# A missing .CMD path must not pick a different installation from PATH.
set +e
PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="$TMP/missing/shimcodex.CMD" \
PORCH_RUN_DIR="$TMP/run-ask-missing-cmd" \
"$PORCH" review ask --progress none -a codex "review this" \
>"$TMP/ask-missing-cmd.out" 2>"$TMP/ask-missing-cmd.err"
missing_cmd_rc=$?
set -e
assert_eq "missing .CMD sibling fails instead of searching PATH" "$missing_cmd_rc" "3"
assert_not_contains "missing .CMD sibling does not run another CLI" \
"$(cat "$TMP/ask-missing-cmd.out")" "FAKE_CODEX_OK"

# Other extensions do not imply an npm shim, even when a sibling exists.
set +e
PATH="$SHIM_DIR:$PATH" PORCH_BIN_CODEX="$SHIM_DIR/shimcodex.exe" \
PORCH_RUN_DIR="$TMP/run-ask-missing-exe" \
"$PORCH" review ask --progress none -a codex "review this" \
>"$TMP/ask-missing-exe.out" 2>"$TMP/ask-missing-exe.err"
missing_exe_rc=$?
set -e
assert_eq "missing non-.CMD binary still fails" "$missing_exe_rc" "3"
assert_not_contains "missing non-.CMD binary does not run sibling" \
"$(cat "$TMP/ask-missing-exe.out")" "FAKE_CODEX_OK"

echo "=== Delegate with fake ==="
export PORCH_RUN_DIR="$TMP/run-del"
mkdir -p "$PORCH_RUN_DIR"
Expand Down
Loading