From 18a84c33740c7b0965c6a3d2e690fad128a4b518 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 1 Nov 2026 14:39:51 +0000 Subject: [PATCH] fix(gbrain-detect): let --doctor's report drain before exit process.exit() cut a large verdict off mid-pipe, so Step 5's jq read truncated JSON and stopped on a healthy brain. Set exitCode instead. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-gbrain-detect | 19 ++++++++++--------- test/setup-gbrain-doctor-gate.test.ts | 11 +++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/bin/gstack-gbrain-detect b/bin/gstack-gbrain-detect index 00f98a9c2..8e69eaba1 100755 --- a/bin/gstack-gbrain-detect +++ b/bin/gstack-gbrain-detect @@ -351,14 +351,6 @@ function main(): void { // callers — setup, bin/dev-setup, and `gstack-config gbrain-refresh` — can // decide whether to render the gbrain :user variant without duplicating the // JSON grep. Prints nothing on stdout. -// --doctor: the full brain-health gate /setup-gbrain Step 5 runs. Prints the -// DoctorVerdict as JSON and exits 0 iff brain_ok. -if (process.argv.includes("--doctor")) { - const verdict = runDoctor([], 300_000); - process.stdout.write(JSON.stringify(verdict, null, 2) + "\n"); - process.exit(verdict.brain_ok ? 0 : 1); -} - if (process.argv.includes("--is-ok")) { const noCache = process.env.GSTACK_DETECT_NO_CACHE === "1"; const status = localEngineStatus({ noCache }); @@ -370,4 +362,13 @@ if (process.argv.includes("--is-ok")) { ); } -main(); +// --doctor: the full brain-health gate /setup-gbrain Step 5 runs. Prints the +// DoctorVerdict as JSON and exits 0 iff brain_ok. exitCode, not process.exit: +// exit() cuts off a large report still draining into a pipe. +if (process.argv.includes("--doctor")) { + const verdict = runDoctor([], 300_000); + process.stdout.write(JSON.stringify(verdict, null, 2) + "\n"); + process.exitCode = verdict.brain_ok ? 0 : 1; +} else { + main(); +} diff --git a/test/setup-gbrain-doctor-gate.test.ts b/test/setup-gbrain-doctor-gate.test.ts index b8368d861..47ce08ea0 100644 --- a/test/setup-gbrain-doctor-gate.test.ts +++ b/test/setup-gbrain-doctor-gate.test.ts @@ -141,6 +141,17 @@ describe('gstack-gbrain-detect --doctor', () => { expect(JSON.parse(r.stdout)).toMatchObject({ brain_ok: false, brain_failures: ['pgvector'] }); }); + test('prints a large report whole through a pipe', () => { + const big = { status: 'healthy', checks: Array.from({ length: 3000 }, (_, i) => ({ ...check(`c${i}`, 'brain'), message: 'x'.repeat(60) })) }; + const env = { ...caseEnv('all checks pass'), FAKE_DOCTOR_DIR: path.join(tmp, 'big') }; + fs.mkdirSync(env.FAKE_DOCTOR_DIR, { recursive: true }); + fs.writeFileSync(path.join(env.FAKE_DOCTOR_DIR, 'scoped.json'), JSON.stringify(big)); + for (let i = 0; i < 3; i++) { + const r = spawnSync('bash', ['-c', `"${DETECT}" --doctor | cat`], { env, encoding: 'utf-8', timeout: 30_000 }); + expect(JSON.parse(r.stdout).report.checks).toHaveLength(3000); + } + }); + test('exits 1 with the cause when doctor prints no JSON', () => { const r = spawnSync(DETECT, ['--doctor'], { env: caseEnv('doctor prints no JSON'), encoding: 'utf-8', timeout: 30_000 }); expect(r.status).toBe(1);