From 0e2e112182f79c424d789bdc988de8408643058b Mon Sep 17 00:00:00 2001 From: garrytan Date: Wed, 28 Oct 2026 11:51:23 +0000 Subject: [PATCH] fix: pre-landing review fixes The doctor's in-process config read reselected the state root after the doctor set GSTACK_STATE_ROOT, so --check and --json printed 'selected by GSTACK_STATE_ROOT' whatever selected the root. cfg now keeps _gstack_sr_var. Adds the regression test, a CRLF/blank/200-byte launch-probe detail test, and corrects the clamp comment. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-doctor | 4 ++-- test/gstack-doctor.test.ts | 14 ++++++++++++++ test/gstack-launch-probe.test.ts | 8 ++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/bin/gstack-doctor b/bin/gstack-doctor index 94e3ce18d..0690fb3a9 100755 --- a/bin/gstack-doctor +++ b/bin/gstack-doctor @@ -93,14 +93,14 @@ row() { sq() { printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"; } # clamp CHARSET MAX — stdin's first line reduced to CHARSET, at most MAX chars. -# Builtins only, bytewise like tr and cut: every caller pipes into it, and on +# Builtins only, bytewise like tr and cut: every caller feeds it stdin, and on # Windows a head | tr | cut chain costs three process starts (~60 ms). clamp() { local LC_ALL=C line pat="[^$1]"; IFS= read -r line; line=${line//$pat/}; printf '%s' "${line:0:$2}"; } # cfg KEY DEFAULT — sets _cfg to KEY as `gstack-config get` reads it, or DEFAULT # (gstack-config's own default) when unset. In-process through # gstack-state-root.sh, sourced below: a gstack-config start costs ~200 ms on Windows. -cfg() { gstack_config_select "$1" 2>/dev/null; _cfg="${_gstack_cfg_value:-$2}"; } +cfg() { local sr_var=${_gstack_sr_var-}; gstack_config_select "$1" 2>/dev/null; _gstack_sr_var=$sr_var; _cfg="${_gstack_cfg_value:-$2}"; } # epoch_of VALUE — epoch seconds for an epoch or an ISO-8601 UTC time; "" otherwise. epoch_of() { diff --git a/test/gstack-doctor.test.ts b/test/gstack-doctor.test.ts index 3e04ef275..dc81a8c06 100644 --- a/test/gstack-doctor.test.ts +++ b/test/gstack-doctor.test.ts @@ -507,6 +507,20 @@ describe('gstack-doctor --check', () => { expect(probeCalls(f)).not.toContain('probe-model'); }); + // Value: protects=the --check state-root row names the variable that selected the root, after earlier rows read config; + // fails_when=an in-process config read reselects the root from the doctor's own GSTACK_STATE_ROOT; + // why_new=every other fixture sets GSTACK_STATE_ROOT itself, so a wrong "selected by" still reads GSTACK_STATE_ROOT; seam=none; + // no_claim=CLAUDE_PLUGIN_DATA and default selection (GSTACK_HOME stands in for every selector other than GSTACK_STATE_ROOT) + test('the state-root row names GSTACK_HOME when GSTACK_HOME selected the root, in text and JSON', () => { + const f = makeFixture(); + installCodex(f); + const byHome = { GSTACK_STATE_ROOT: '', GSTACK_HOME: f.state }; + const r = row(f, [], byHome); + expect(r.rows['state-root'].rest, r.out).toContain('(selected by GSTACK_HOME;'); + const json = JSON.parse(doctor(f, ['--json'], byHome).out); + expect(json.rows.find((x: { id: string }) => x.id === 'state-root').detail).toContain('(selected by GSTACK_HOME;'); + }); + test('--for autoplan: healthy Capy machine with Claude subagents as the outside voice and no Codex CLI passes', () => { const f = makeFixture(); installCodex(f); diff --git a/test/gstack-launch-probe.test.ts b/test/gstack-launch-probe.test.ts index fcf76bc36..5acc2c580 100644 --- a/test/gstack-launch-probe.test.ts +++ b/test/gstack-launch-probe.test.ts @@ -51,6 +51,14 @@ describe.skipIf(process.platform === 'win32')('gstack-launch-probe.sh classifica expect(probe(fakeBinary('echo "Unknown argument: --version" >&2; exit 3'))).toEqual({ state: 'broken', detail: 'exit 3: Unknown argument: --version' }); }); + // Value: protects=the detail is the first non-blank line with CRs dropped, cut to 200 bytes (Windows CRLF output); + // fails_when=the builtin line picker keeps a CR, returns a blank line or cuts at another length; + // why_new=every other case prints one clean LF line; seam=none; no_claim=real Git Bash output (a sh fake prints the bytes) + test('a broken binary\'s detail is its first non-blank line, CRs dropped, at most 200 bytes', () => { + const long = 'y'.repeat(250); + expect(probe(fakeBinary(`printf '\\r\\n \\n\\t\\r\\n${long}\\r\\nnext\\n'; exit 3`))).toEqual({ state: 'broken', detail: `exit 3: ${'y'.repeat(200)}` }); + }); + test('an absent binary is missing', () => { expect(probe(fakeBinary(null)).state).toBe('missing'); });