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>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-08 07:18:44 +00:00
1 parent 3f797c0bf7
commit 1187917af3
4 files changed
+24 -21

No files matched your search

+1 -2
View File
@@ -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
}
+9 -13
View File
@@ -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))"
+4 -3
View File
@@ -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');
+10 -3
View File
@@ -44,12 +44,13 @@ function probe(tools: Record<string, string>) {
].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-');