Files
gstack/bin/gstack-launch-probe.sh
T
garrytanandcapy-ai[bot] 1fa01610ca feat(windows): detect Smart App Control-blocked binaries in setup and gstack-doctor (#2595, #2124)
Smart App Control refuses the five unsigned binaries gstack compiles
locally, and Git Bash reports it as 'Permission denied', so setup's
[ -x ] gate passed and skills failed later. bin/gstack-launch-probe.sh
runs each binary's side-effect-free --version and classifies it:
native, blocked (spawn error, exit 126/127, or an application-control
message), broken (launched, exited non-zero) or missing. It shows the
raw first output line, since the exact Windows text is unverified.

On Windows, setup names each blocked binary and the skills that need it,
cites #2595, says which shell ran setup and whether WSL is installed,
gives today's workarounds (WSL, or turning Smart App Control off), how to
undo, and the new troubleshooting section. gstack-doctor's browse row now
says 'blocked at launch' instead of passing, a binaries row covers the
other four, and a cso row reads the build-result record.

windows-setup-e2e builds with GSTACK_STRICT_BUILD=1, checks all five
binaries answer --version, and adds two forced CSO failures: fatal under
strict mode, and a completed setup that reports the failure and a doctor
cso warning under GSTACK_STRICT_BUILD=0.

No shim or launcher: it could not be called from PowerShell and would
load the caller's project .env/bunfig. Signing is the follow-up in
TODOS.md. Diagnosis credit: @knetics9000 (#2595, #2596), @smartjelic-sys
(#2595), @pstilwell90 (#2124), @cko32002 (#2265), @salluexez (#2127).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 06:30:48 +00:00

73 lines
2.9 KiB
Bash

# shellcheck shell=bash
# gstack-launch-probe.sh — sourced by setup and bin/gstack-doctor.
#
# Windows Smart App Control (and other application-control policies) blocks
# unsigned executables at launch, and Git Bash reports that as a plain
# "Permission denied" (#2595, #2124). `[ -x ]` cannot see it, so this runs
# each compiled binary's side-effect-free `--version` and classifies it:
#
# native launched and exited 0
# blocked could not launch: spawn error, permission denied (exit 126/127),
# or an application-control message in its output
# broken launched but exited non-zero or timed out: a build problem
# missing not built
#
# The exact Windows error text is unverified on a real SAC machine, so callers
# show the raw first output line ($_glp_detail) beside the classification.
# Bash 3.2 plus POSIX tools.
GSTACK_COMPILED_BINARIES="browse/dist/browse browse/dist/find-browse design/dist/design make-pdf/dist/pdf bin/gstack-global-discover"
# gstack_launch_probe PATH — sets _glp_state and _glp_detail.
gstack_launch_probe() {
local out rc
_glp_state=missing
_glp_detail=""
[ -e "$1" ] || return 0
if command -v timeout >/dev/null 2>&1; then
out=$(timeout 20 "$1" --version </dev/null 2>&1)
else
out=$("$1" --version </dev/null 2>&1)
fi
rc=$?
_glp_detail=$(printf '%s\n' "$out" | tr -d '\r' | grep -v '^[[:space:]]*$' | head -1 | cut -c1-200)
case "$out" in
*[Aa]pplication\ [Cc]ontrol*|*"Smart App Control"*|*"blocked by group policy"*|*"has blocked this file"*)
_glp_state=blocked; return 0 ;;
esac
case "$rc" in
0) _glp_state=native ;;
126|127) _glp_state=blocked ;;
124) _glp_state=broken; _glp_detail="timed out after 20s${_glp_detail:+: $_glp_detail}" ;;
*) _glp_state=broken; _glp_detail="exit $rc${_glp_detail:+: $_glp_detail}" ;;
esac
}
# gstack_launch_probe_skills REL — the skills that stop working without it.
gstack_launch_probe_skills() {
case "$1" in
browse/dist/browse|browse/dist/find-browse) printf '%s' "/browse, /qa, /qa-only, /design-review, /canary, /benchmark, /pair-agent, /scrape, /make-pdf" ;;
design/dist/design) printf '%s' "/design-consultation, /design-shotgun, /design-html, /plan-design-review" ;;
make-pdf/dist/pdf) printf '%s' "/make-pdf" ;;
bin/gstack-global-discover) printf '%s' "/retro global" ;;
esac
}
# gstack_launch_probe_all ROOT EXE — probes every compiled binary under ROOT
# (EXE is ".exe" on Windows). Sets _glp_blocked and _glp_broken to
# newline-separated "rel<TAB>detail" rows, and _glp_missing to a list.
gstack_launch_probe_all() {
local rel
_glp_blocked="" _glp_broken="" _glp_missing=""
for rel in $GSTACK_COMPILED_BINARIES; do
gstack_launch_probe "$1/$rel$2"
case "$_glp_state" in
blocked) _glp_blocked="$_glp_blocked$rel $_glp_detail
" ;;
broken) _glp_broken="$_glp_broken$rel $_glp_detail
" ;;
missing) _glp_missing="${_glp_missing:+$_glp_missing, }$rel" ;;
esac
done
}