From 2388e6ab34a01a26b9246d228246b3a14de3ab88 Mon Sep 17 00:00:00 2001 From: Michel Moreira Date: Mon, 7 Sep 2026 11:34:37 -0300 Subject: [PATCH] fix(#4342): run the bug-167 routing test in a fixture project, not the developer's (#4387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test called runGsdTools with its default cwd — the test process's own working directory — and an inherited HOME, so the child read the checkout's real .planning/ and the developer's real ~/.gsd/defaults.json. testEnvBase() blanks the config-LOCATION env keys but sandboxes neither cwd nor HOME. On a checkout that has workstreams with no active pointer, `init.progress` exits non-zero and the FIRST assertion fails, so the routing comparison the test exists for was never evaluated: init.progress failed: Error: init.progress requires a workstream in workstream mode — no active workstream is set ... Available workstreams: alpha Reproduced byte-for-byte by adding .planning/workstreams/alpha/ to the checkout: red on next, green here. The invariant under test — `query ` and `` returning identical payloads — is independent of project state, so a plain createTempProject() fixture is enough, with HOME/USERPROFILE pointed at it (the idiom runGsdTools's own doc comment prescribes). Two assertions pin the sandbox deterministically rather than conditionally: the fixture HAS a .planning/ and the repo checkout does not, so dropping the cwd override fails on every lane — CI included, where the ambient state that exposed the bug is absent. Co-authored-by: Tom Boucher --- tests/command-routing-hub.test.cjs | 38 ++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/tests/command-routing-hub.test.cjs b/tests/command-routing-hub.test.cjs index f434419fb..26b97eac4 100644 --- a/tests/command-routing-hub.test.cjs +++ b/tests/command-routing-hub.test.cjs @@ -962,20 +962,48 @@ describe('CommandRoutingHub — exitReason? field on InvalidArgs (#1644 / amendm const { test } = require('node:test'); const assert = require('node:assert/strict'); -const { runGsdTools } = require('./helpers.cjs'); +const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); -test('bug #167: query meta-command prefixes direct gsd-tools calls', () => { - const direct = runGsdTools(['init.progress']); +// #4342: this ran gsd-tools with runGsdTools's default cwd — the checkout's own +// working directory — and an inherited HOME, so the child read the developer's +// real .planning/config.json and ~/.gsd/defaults.json. On a machine whose real +// project enables workstream mode with no active workstream, `init.progress` +// exits non-zero and the FIRST assertion fails, so the routing comparison this +// test exists for was never reached. testEnvBase() blanks the config-LOCATION +// env keys but sandboxes neither cwd nor HOME. +// +// The routing invariant is about `query ` and `` agreeing, which is +// independent of project state — so the fixture only has to be a project whose +// state is known and unaffected by the developer's. +test('bug #167: query meta-command prefixes direct gsd-tools calls', (t) => { + const fixture = createTempProject('gsd-4342-routing-'); + t.after(() => cleanup(fixture)); + // HOME/USERPROFILE point at the fixture so ~/.gsd/defaults.json resolves + // inside it (absent) rather than in the developer's home — the idiom + // runGsdTools's own doc comment prescribes for exactly this. + const sandbox = { HOME: fixture, USERPROFILE: fixture }; + + const direct = runGsdTools(['init.progress'], fixture, sandbox); assert.equal(direct.success, true, `init.progress failed: ${direct.error || direct.output}`); - const meta = runGsdTools(['query', 'init.progress']); + const meta = runGsdTools(['query', 'init.progress'], fixture, sandbox); assert.equal(meta.success, true, `query init.progress failed: ${meta.error || meta.output}`); + const directPayload = JSON.parse(direct.output); assert.deepEqual( JSON.parse(meta.output), - JSON.parse(direct.output), + directPayload, 'query-prefixed and direct invocations should return identical init.progress payloads' ); + + // Pin the sandbox itself, deterministically rather than conditionally: the + // fixture HAS a .planning/ and the repo checkout does NOT, so if the cwd + // override is ever dropped this fails on every lane — including CI, where the + // ambient state that exposed the bug is absent. + assert.equal(directPayload.planning_exists, true, + 'the child must run in the fixture project, not in the checkout'); + assert.equal(directPayload.phase_count, 0, + 'the fixture has no phases — a non-zero count means a real project was read'); }); }); }