From 077d70fec9e6d42103d04454c01f6c7b46fd3549 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 1 Nov 2026 14:30:53 +0000 Subject: [PATCH] fix(gbrain-detect): a doctor spawn that throws fails closed instead of crashing detect A JSON-null ~/.gbrain/config.json made spawnGbrain throw, which crashed detect before Step 1.5's broken-config remediation could run. Step 5 prose now says skill checks never count, matching the code. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-gbrain-detect | 8 +++++++- setup-gbrain/SKILL.md | 2 +- setup-gbrain/SKILL.md.tmpl | 2 +- test/setup-gbrain-doctor-gate.test.ts | 14 ++++++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/bin/gstack-gbrain-detect b/bin/gstack-gbrain-detect index 548f61617..2ad615e1a 100755 --- a/bin/gstack-gbrain-detect +++ b/bin/gstack-gbrain-detect @@ -145,7 +145,13 @@ interface DoctorVerdict { } function runDoctor(extraArgs: string[], timeoutMs: number): DoctorVerdict { - const r = spawnGbrain(["doctor", "--json", ...extraArgs, "--scope=brain"], { timeout: timeoutMs }); + let r: ReturnType; + try { + r = spawnGbrain(["doctor", "--json", ...extraArgs, "--scope=brain"], { timeout: timeoutMs }); + } catch (e) { + const error = `gbrain doctor could not start: ${e instanceof Error ? e.message : String(e)}`; + return { brain_ok: false, status: null, brain_failures: [], skill_failures: [], report: null, error }; + } let report: { status?: string; checks?: { name?: string; status?: string; category?: string }[] } | null = null; try { report = JSON.parse(r.stdout || "null"); diff --git a/setup-gbrain/SKILL.md b/setup-gbrain/SKILL.md index 814c67247..f0d86090a 100644 --- a/setup-gbrain/SKILL.md +++ b/setup-gbrain/SKILL.md @@ -319,7 +319,7 @@ DOCTOR=$(~/.claude/skills/gstack/bin/gstack-gbrain-detect --doctor) BRAIN_OK=$(echo "$DOCTOR" | jq -r .brain_ok) ``` -If `BRAIN_OK` is `true`, proceed; only brain checks count. Note any +If `BRAIN_OK` is `true`, proceed; skill checks never count. Note any `skill_failures`: gbrain graded a skills dir it found, maybe another agent's (`~/.openclaw/workspace`). Never edit that workspace to pass. Otherwise show `echo "$DOCTOR" | jq .` and STOP. diff --git a/setup-gbrain/SKILL.md.tmpl b/setup-gbrain/SKILL.md.tmpl index 68fcbb28d..5e3a96fd8 100644 --- a/setup-gbrain/SKILL.md.tmpl +++ b/setup-gbrain/SKILL.md.tmpl @@ -234,7 +234,7 @@ DOCTOR=$(~/.claude/skills/gstack/bin/gstack-gbrain-detect --doctor) BRAIN_OK=$(echo "$DOCTOR" | jq -r .brain_ok) ``` -If `BRAIN_OK` is `true`, proceed; only brain checks count. Note any +If `BRAIN_OK` is `true`, proceed; skill checks never count. Note any `skill_failures`: gbrain graded a skills dir it found, maybe another agent's (`~/.openclaw/workspace`). Never edit that workspace to pass. Otherwise show `echo "$DOCTOR" | jq .` and STOP. diff --git a/test/setup-gbrain-doctor-gate.test.ts b/test/setup-gbrain-doctor-gate.test.ts index 5e703780b..b8368d861 100644 --- a/test/setup-gbrain-doctor-gate.test.ts +++ b/test/setup-gbrain-doctor-gate.test.ts @@ -150,6 +150,20 @@ describe('gstack-gbrain-detect --doctor', () => { }); }); +describe('gstack-gbrain-detect with a JSON-null gbrain config', () => { + test('still prints detect JSON, and --doctor fails closed with a cause', () => { + const env = { ...caseEnv('all checks pass'), HOME: path.join(tmp, 'null-config-home') }; + fs.mkdirSync(path.join(env.HOME, '.gbrain'), { recursive: true }); + fs.writeFileSync(path.join(env.HOME, '.gbrain', 'config.json'), 'null'); + const detect = spawnSync(DETECT, [], { env, encoding: 'utf-8', timeout: 30_000 }); + expect(detect.status).toBe(0); + expect(JSON.parse(detect.stdout).gbrain_doctor_ok).toBe(false); + const doctor = spawnSync(DETECT, ['--doctor'], { env, encoding: 'utf-8', timeout: 30_000 }); + expect(doctor.status).toBe(1); + expect(JSON.parse(doctor.stdout).error).toContain('could not start'); + }); +}); + describe('gstack-gbrain-detect gbrain_doctor_ok', () => { for (const name of Object.keys(CASES)) { const healthy = HEALTHY.includes(name);