From 1187917af3c9df331d99051d49e69f2b00d461f0 Mon Sep 17 00:00:00 2001 From: garrytan Date: Thu, 8 Oct 2026 07:18:44 +0000 Subject: [PATCH] fix(setup): never write the CSO build record into a source checkout that needed no build Setup wrote bin/.gstack-cso-build-result for every probe-stage outcome, so a run that built nothing still changed the source checkout's bin/ (caught by setup-codex-scope's unchanged-source checks). A missing prerequisite now comes from the run's own probe, as before, and setup only removes a stale record alongside the removed helper. scripts/build-cso.sh remains the only writer. The doctor's cso row reports 'native helper not built' with no record. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-doctor | 3 +-- setup | 22 +++++++++------------- test/gstack-doctor.test.ts | 7 ++++--- test/setup-cso-degrade.test.ts | 13 ++++++++++--- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/bin/gstack-doctor b/bin/gstack-doctor index 6606a90dc..065c96bc0 100755 --- a/bin/gstack-doctor +++ b/bin/gstack-doctor @@ -334,7 +334,6 @@ check_cso() { case "$result" in ok) if [ "$usable" -eq 1 ]; then row ok cso "native helper $installed" else row warn cso "the last build succeeded but the native helper is missing; /cso reports not assessed" "cd $SRC && ./setup"; fi ;; - unavailable) row "not configured" cso "unavailable: a build prerequisite is missing ($reason${diag:+: $diag}); /cso reports not assessed" "cd $SRC && ./setup names the prerequisite to install" ;; failed) if [ "$usable" -eq 1 ] && [ -n "$installed" ]; then row warn cso "$stage step failed ($reason) for $(cso_field revision); previous CSO kept: $installed" "cd $SRC && bun run build:cso && ./setup (log: ${diag:-none}; $DOCS#cso-build-or-publish-failed)" @@ -342,7 +341,7 @@ check_cso() { row warn cso "unavailable: the $stage step failed ($reason); /cso reports not assessed" "cd $SRC && bun run build:cso && ./setup (log: ${diag:-none}; $DOCS#cso-build-or-publish-failed)" fi ;; *) if [ "$usable" -eq 1 ]; then row ok cso "native helper built (no build record yet)" - else row "not configured" cso "native helper not built; /cso reports not assessed" "cd $SRC && ./setup"; fi ;; + else row "not configured" cso "native helper not built; /cso reports not assessed" "cd $SRC && ./setup (it names any missing build prerequisite)"; fi ;; esac } diff --git a/setup b/setup index 7a17debad..eb538e214 100755 --- a/setup +++ b/setup @@ -1698,13 +1698,11 @@ _EXE="" if [ "$IS_WINDOWS" -eq 1 ]; then _EXE=".exe"; fi # Never leave a trusted helper from an earlier toolchain/version available when # this setup run cannot reproduce it. The /cso skill then reports not assessed. -# The build-result record (written by scripts/build-cso.sh for build and -# publish) gets the probe outcome here; the summary and gstack-doctor read it. +# An earlier build's result record no longer describes the removed helper; +# setup never writes into the source checkout when nothing is built. if [ "$CSO_BUILD_AVAILABLE" -eq 0 ]; then - mkdir -p "$SOURCE_GSTACK_DIR/bin" - printf 'result=unavailable\nstage=probe\nreason=%s\nrevision=%s\ninstalled=\nlauncher=no\ndiagnostic=%s\n' \ - "$CSO_FAIL_REASON" "$SETUP_VERSION" "$CSO_PROBE_DETAIL" > "$SOURCE_GSTACK_DIR/bin/.gstack-cso-build-result" || true - rm -f "$SOURCE_GSTACK_DIR/bin/gstack-cso-launcher" \ + rm -f "$SOURCE_GSTACK_DIR/bin/.gstack-cso-build-result" \ + "$SOURCE_GSTACK_DIR/bin/gstack-cso-launcher" \ "$SOURCE_GSTACK_DIR/bin/gstack-cso-launcher.exe" \ "$SOURCE_GSTACK_DIR/bin/gstack-cso-core" \ "$SOURCE_GSTACK_DIR/bin/gstack-cso-core.exe" \ @@ -4412,13 +4410,11 @@ fi _setup_print_summary # ─── CSO native-helper summary ──────────────────────────────────────────────── -# From bin/.gstack-cso-build-result, the record gstack-doctor also reads: the -# probe outcome (written above) or scripts/build-cso.sh's build/publish result. +# A missing prerequisite comes from this run's probe. A build or publish +# failure comes from bin/.gstack-cso-build-result, which scripts/build-cso.sh +# writes and gstack-doctor also reads. _cso_field() { sed -n "s/^$1=//p" "$SOURCE_GSTACK_DIR/bin/.gstack-cso-build-result" 2>/dev/null | head -1; } -_CSO_RESULT="$(_cso_field result)" -if [ "$_CSO_RESULT" = "unavailable" ]; then - CSO_FAIL_REASON="$(_cso_field reason)" - CSO_PROBE_DETAIL="$(_cso_field diagnostic)" +if [ "$CSO_BUILD_AVAILABLE" -eq 0 ]; then case "$CSO_FAIL_REASON" in bun-compile-flags) _CSO_PREREQ="upgrade Bun to a release supporting all four --no-compile-autoload-* build flags" ;; c-compiler) _CSO_PREREQ="install a C compiler (cc, Clang, or GCC)" ;; @@ -4437,7 +4433,7 @@ if [ "$_CSO_RESULT" = "unavailable" ]; then log "CSO unavailable: its native helper was not built ($CSO_FAIL_REASON)." log " Failed stage: probe. /cso will report not assessed and the install prerequisite; it will not use repository tooling." log " Everything else is installed and works. To enable /cso, $_CSO_PREREQ, then re-run ./setup." -elif [ "$_CSO_RESULT" = "failed" ]; then +elif [ "$(_cso_field result)" = "failed" ]; then _CSO_STAGE="$(_cso_field stage)" _CSO_INSTALLED="$(_cso_field installed)" _CSO_WHAT="failed ($(_cso_field reason))" diff --git a/test/gstack-doctor.test.ts b/test/gstack-doctor.test.ts index 93ed00e83..0665406b6 100644 --- a/test/gstack-doctor.test.ts +++ b/test/gstack-doctor.test.ts @@ -407,9 +407,10 @@ describe('gstack-doctor launch and CSO rows', () => { test('the CSO row reads the build-result record', () => { const f = makeFixture(); - expect(doctor(f).row('cso').state).toBe('not configured'); - record(f, 'result=unavailable\nstage=probe\nreason=windows-msvc-toolchain\nrevision=1\ninstalled=\nlauncher=no\ndiagnostic=\n'); - expect(doctor(f).row('cso').detail).toContain('unavailable: a build prerequisite is missing (windows-msvc-toolchain)'); + const none = doctor(f).row('cso'); + expect(none.state).toBe('not configured'); + expect(none.detail).toBe('native helper not built; /cso reports not assessed'); + expect(none.fix).toContain('./setup (it names any missing build prerequisite)'); record(f, 'result=failed\nstage=build\nreason=exit 42\nrevision=1.2 (abc)\ninstalled=\nlauncher=no\ndiagnostic=/x/cso.log\n'); let r = doctor(f); expect(r.row('cso').state).toBe('warn'); diff --git a/test/setup-cso-degrade.test.ts b/test/setup-cso-degrade.test.ts index 72a490d86..6d0430b2b 100644 --- a/test/setup-cso-degrade.test.ts +++ b/test/setup-cso-degrade.test.ts @@ -44,12 +44,13 @@ function probe(tools: Record) { ].join('\n'), { env: { ...process.env, PATH: `${dir}:${process.env.PATH}` }, timeout: 30_000 }); } -function summary(record: string | null, opts: { launcher?: boolean } = {}) { +function summary(record: string | null, opts: { launcher?: boolean; probe?: [reason: string, detail: string] } = {}) { const dir = temp('gstack-cso-summary-'); fs.mkdirSync(path.join(dir, 'bin')); if (record !== null) fs.writeFileSync(path.join(dir, 'bin/.gstack-cso-build-result'), record); if (opts.launcher) for (const name of ['gstack-cso-launcher', 'gstack-cso-core']) stub(path.join(dir, 'bin'), name, 'echo OLD'); - const r = runBashScript(['QUIET=0', '_EXE=""', `SOURCE_GSTACK_DIR="${dir}"`, fn('log').trim() || 'log() { echo "$@"; }', SUMMARY].join('\n'), { timeout: 15_000 }); + const probe = opts.probe ? `CSO_BUILD_AVAILABLE=0\nCSO_FAIL_REASON='${opts.probe[0]}'\nCSO_PROBE_DETAIL='${opts.probe[1]}'` : 'CSO_BUILD_AVAILABLE=1'; + const r = runBashScript(['QUIET=0', '_EXE=""', `SOURCE_GSTACK_DIR="${dir}"`, probe, 'log() { echo "$@"; }', SUMMARY].join('\n'), { timeout: 15_000 }); expect(r.status).toBe(0); return { out: r.stdout, dir }; } @@ -82,7 +83,7 @@ describe.skipIf(process.platform === 'win32')('setup: CSO summary reads the buil `result=failed\nstage=${stage}\nreason=${reason}\nrevision=1.91.99.0 (abcdef123456)\ninstalled=${installed}\nlauncher=${installed ? 'yes' : 'no'}\ndiagnostic=/src/bin/.gstack-cso-build.log\n`; test('a probe failure keeps the prerequisite instruction and names the probe stage', () => { - const { out } = summary('result=unavailable\nstage=probe\nreason=windows-toolchain-probe\nrevision=1.0\ninstalled=\nlauncher=no\ndiagnostic=pwsh exited 139: Fatal error\n'); + const { out } = summary(null, { probe: ['windows-toolchain-probe', 'pwsh exited 139: Fatal error'] }); expect(out).toContain('CSO unavailable: its native helper was not built (windows-toolchain-probe).'); expect(out).toContain('Failed stage: probe.'); expect(out).toContain('fix the PowerShell toolchain probe failure (pwsh exited 139: Fatal error)'); @@ -119,6 +120,12 @@ describe.skipIf(process.platform === 'win32')('setup: CSO summary reads the buil }); }); +test('a probe failure removes a stale build record and writes nothing into the source checkout', () => { + const block = between('if [ "$CSO_BUILD_AVAILABLE" -eq 0 ]; then\n rm -f', '\nfi\n'); + expect(block).toContain('rm -f "$SOURCE_GSTACK_DIR/bin/.gstack-cso-build-result"'); + expect(block).not.toContain('>'); +}); + describe.skipIf(process.platform === 'win32')('setup: an incomplete CSO set is fatal only under GSTACK_STRICT_BUILD=1 (#3071)', () => { function gate(strict?: string) { const dir = temp('gstack-cso-gate-');