From 875b257c18a70898a9b6765e98ba6c1cdf2228b0 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 15 Apr 2026 14:59:15 -0400 Subject: [PATCH] fix(init): embed auto_advance/auto_chain_active/mode in init plan-phase output (#2228) Prevents infinite config-get loops on Kimi K2.5 and other models that re-execute bash tool calls when they encounter config-get subshell patterns. Values are now bundled into the init plan-phase JSON so step 15 of plan-phase.md can read them directly without separate shell calls. Closes #2192 Co-authored-by: Claude Sonnet 4.6 --- get-shit-done/bin/lib/core.cjs | 3 ++ get-shit-done/bin/lib/init.cjs | 6 +++ get-shit-done/workflows/plan-phase.md | 14 +++--- tests/init.test.cjs | 68 +++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index c1a637b24..9a1dfa077 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -377,6 +377,9 @@ function loadConfig(cwd) { exa_search: get('exa_search') ?? defaults.exa_search, tdd_mode: get('tdd_mode', { section: 'workflow', field: 'tdd_mode' }) ?? false, text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode, + auto_advance: get('auto_advance', { section: 'workflow', field: 'auto_advance' }) ?? false, + _auto_chain_active: get('_auto_chain_active', { section: 'workflow', field: '_auto_chain_active' }) ?? false, + mode: get('mode') ?? 'interactive', sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos, resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids, context_window: get('context_window') ?? defaults.context_window, diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index 6dfddd479..10b8633d2 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -238,6 +238,12 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) { nyquist_validation_enabled: config.nyquist_validation, commit_docs: config.commit_docs, text_mode: config.text_mode, + // Auto-advance config — included so workflows don't need separate config-get + // calls for these values, which causes infinite config-read loops on some models + // (e.g. Kimi K2.5). See #2192. + auto_advance: !!(config.auto_advance), + auto_chain_active: !!(config._auto_chain_active), + mode: config.mode || 'interactive', // Phase info phase_found: !!phaseInfo, diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md index 07615a9fe..ff4d54cd0 100644 --- a/get-shit-done/workflows/plan-phase.md +++ b/get-shit-done/workflows/plan-phase.md @@ -1145,20 +1145,20 @@ Route to `` OR `auto_advance` depending on flags/config. ## 15. Auto-Advance Check -Check for auto-advance trigger: +Check for auto-advance trigger using values already loaded in step 1: 1. Parse `--auto` and `--chain` flags from $ARGUMENTS -2. **Sync chain flag with intent** — if user invoked manually (no `--auto` and no `--chain`), clear the ephemeral chain flag from any previous interrupted `--auto` chain. This does NOT touch `workflow.auto_advance` (the user's persistent settings preference): +2. Use `auto_chain_active` and `auto_advance` from the INIT JSON parsed in step 1 — **do not issue additional `config-get` calls for these values** (they are already present in the init output). Issuing redundant `config-get` calls for values already in INIT can cause infinite read loops on some runtimes. +3. **Sync chain flag with intent** — if user invoked manually (no `--auto` and no `--chain`), clear the ephemeral chain flag from any previous interrupted `--auto` chain. This does NOT touch `workflow.auto_advance` (the user's persistent settings preference): ```bash if [[ ! "$ARGUMENTS" =~ --auto ]] && [[ ! "$ARGUMENTS" =~ --chain ]]; then node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-set workflow._auto_chain_active false 2>/dev/null fi ``` -3. Read both the chain flag and user preference: - ```bash - AUTO_CHAIN=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow._auto_chain_active 2>/dev/null || echo "false") - AUTO_CFG=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.auto_advance 2>/dev/null || echo "false") - ``` + +Set local variables from INIT (parsed once in step 1): +- `AUTO_CHAIN` = `auto_chain_active` from INIT JSON (boolean, default false) +- `AUTO_CFG` = `auto_advance` from INIT JSON (boolean, default false) **If `--auto` or `--chain` flag present AND `AUTO_CHAIN` is not true:** Persist chain flag to config (handles direct invocation without prior discuss-phase): ```bash diff --git a/tests/init.test.cjs b/tests/init.test.cjs index 07c6ece84..b8d5036f6 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -1358,6 +1358,74 @@ describe('findProjectRoot integration via --cwd', () => { }); }); +// ───────────────────────────────────────────────────────────────────────────── +// #2192: init plan-phase must include auto_advance, auto_chain_active, and mode +// so workflows don't need separate config-get calls that loop on Kimi K2.5 +// ───────────────────────────────────────────────────────────────────────────── + +describe('#2192: init plan-phase includes auto-advance config to prevent separate config-get loops', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject(); + fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-auth'), { recursive: true }); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + ['# Roadmap', '', '## Milestone v1', '', '### Phase 1: Auth', '**Goal:** Auth'].join('\n') + ); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('init plan-phase includes auto_advance field (defaults false)', () => { + const result = runGsdTools('init plan-phase 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.ok('auto_advance' in output, 'init plan-phase must include auto_advance field'); + assert.strictEqual(output.auto_advance, false, 'auto_advance should default to false'); + }); + + test('init plan-phase includes auto_chain_active field (defaults false)', () => { + const result = runGsdTools('init plan-phase 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.ok('auto_chain_active' in output, 'init plan-phase must include auto_chain_active field'); + assert.strictEqual(output.auto_chain_active, false, 'auto_chain_active should default to false'); + }); + + test('init plan-phase includes mode field (defaults to interactive)', () => { + const result = runGsdTools('init plan-phase 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.ok('mode' in output, 'init plan-phase must include mode field'); + assert.strictEqual(output.mode, 'interactive', 'mode should default to interactive'); + }); + + test('init plan-phase reflects auto_advance true when set in config', () => { + const configPath = path.join(tmpDir, '.planning', 'config.json'); + const cfg = { workflow: { auto_advance: true } }; + fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2)); + + const result = runGsdTools('init plan-phase 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.strictEqual(output.auto_advance, true, 'auto_advance should reflect config value'); + }); + + test('init plan-phase reflects auto_chain_active true when set in config', () => { + const configPath = path.join(tmpDir, '.planning', 'config.json'); + const cfg = { workflow: { _auto_chain_active: true } }; + fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2)); + + const result = runGsdTools('init plan-phase 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const output = JSON.parse(result.output); + assert.strictEqual(output.auto_chain_active, true, 'auto_chain_active should reflect config value'); + }); +}); + // ───────────────────────────────────────────────────────────────────────────── // roadmap analyze command // ─────────────────────────────────────────────────────────────────────────────