From 96ce608ee6023675049ea0a2766945e9b8673cdb Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 6 May 2026 14:56:34 -0400 Subject: [PATCH 1/4] fix(config): add resolve_model_ids to VALID_CONFIG_KEYS; accept workflow._auto_chain_active via RUNTIME_STATE_KEYS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #3162 `resolve_model_ids` is a documented top-level config key (CONFIGURATION.md) read by core.cjs and session-runner.ts, but was missing from the CJS and SDK VALID_CONFIG_KEYS allowlists — causing config-set to reject it with "Unknown config key". `workflow._auto_chain_active` is internal runtime state intentionally excluded from VALID_CONFIG_KEYS by #2530, but plan-phase, execute-phase, discuss-phase, transition, and new-project workflows all write it via `config-set`. Without a valid write path these calls emit spurious errors (silenced with `|| true` but noisy in logs). A new RUNTIME_STATE_KEYS set in config-schema.cjs holds keys that isValidConfigKey() accepts without exposing them as user-settable options — preserving the #2530 intent while fixing the runtime error. Co-Authored-By: Claude Sonnet 4.6 --- get-shit-done/bin/lib/config-schema.cjs | 17 +++++++++++++++-- sdk/src/query/config-schema.ts | 2 ++ tests/bug-2530-valid-config-keys.test.cjs | 18 +++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/get-shit-done/bin/lib/config-schema.cjs b/get-shit-done/bin/lib/config-schema.cjs index 2fec078c0..bba9d44b0 100644 --- a/get-shit-done/bin/lib/config-schema.cjs +++ b/get-shit-done/bin/lib/config-schema.cjs @@ -69,6 +69,18 @@ const VALID_CONFIG_KEYS = new Set([ 'claude_md_assembly.mode', // #2517 — runtime-aware model profiles 'runtime', + // #3162 — documented top-level key: controls model ID resolution for non-Claude runtimes + 'resolve_model_ids', +]); + +/** + * Internal runtime-state keys — accepted by config-set (workflows write them) but not + * exposed as user-settable options. Excluded from VALID_CONFIG_KEYS so they stay out of + * the public docs-parity check and the "Valid keys:" error message. + * See: #3162 (workflow._auto_chain_active written by plan/execute/discuss workflows) + */ +const RUNTIME_STATE_KEYS = new Set([ + 'workflow._auto_chain_active', ]); /** @@ -98,11 +110,12 @@ const DYNAMIC_KEY_PATTERNS = [ ]; /** - * Returns true if keyPath is a valid config key (exact or dynamic pattern). + * Returns true if keyPath is a valid config key (exact, dynamic pattern, or runtime state). */ function isValidConfigKey(keyPath) { if (VALID_CONFIG_KEYS.has(keyPath)) return true; + if (RUNTIME_STATE_KEYS.has(keyPath)) return true; return DYNAMIC_KEY_PATTERNS.some((p) => p.test(keyPath)); } -module.exports = { VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS, isValidConfigKey }; +module.exports = { VALID_CONFIG_KEYS, RUNTIME_STATE_KEYS, DYNAMIC_KEY_PATTERNS, isValidConfigKey }; diff --git a/sdk/src/query/config-schema.ts b/sdk/src/query/config-schema.ts index 0029efa6a..d1b245f17 100644 --- a/sdk/src/query/config-schema.ts +++ b/sdk/src/query/config-schema.ts @@ -71,6 +71,8 @@ export const VALID_CONFIG_KEYS: ReadonlySet = new Set([ 'claude_md_assembly.mode', // #2517 — runtime-aware model profiles 'runtime', + // #3162 — documented top-level key: controls model ID resolution for non-Claude runtimes + 'resolve_model_ids', ]); /** diff --git a/tests/bug-2530-valid-config-keys.test.cjs b/tests/bug-2530-valid-config-keys.test.cjs index 78e5c3ad1..257a94358 100644 --- a/tests/bug-2530-valid-config-keys.test.cjs +++ b/tests/bug-2530-valid-config-keys.test.cjs @@ -7,6 +7,8 @@ * #2532 — workflow.ui_review is used in autonomous.md but missing from VALID_CONFIG_KEYS * #2533 — workflow.max_discuss_passes is used in discuss-phase.md but missing from VALID_CONFIG_KEYS * #2535 — sub_repos and plan_checker legacy keys need CONFIG_KEY_SUGGESTIONS migration hints + * #3162 — resolve_model_ids missing from VALID_CONFIG_KEYS; workflow._auto_chain_active must be + * accepted by isValidConfigKey (written by workflows) without being user-visible */ const { describe, test } = require('node:test'); @@ -14,7 +16,7 @@ const assert = require('node:assert/strict'); const path = require('node:path'); const { createTempProject, cleanup, runGsdTools } = require('./helpers.cjs'); -const { VALID_CONFIG_KEYS } = require('../get-shit-done/bin/lib/config-schema.cjs'); +const { VALID_CONFIG_KEYS, isValidConfigKey } = require('../get-shit-done/bin/lib/config-schema.cjs'); describe('VALID_CONFIG_KEYS correctness', () => { test('#2530: workflow._auto_chain_active must not be in VALID_CONFIG_KEYS (internal state)', () => { @@ -44,6 +46,20 @@ describe('VALID_CONFIG_KEYS correctness', () => { 'workflow.max_discuss_passes is read in discuss-phase.md via gsd-sdk query config-get' ); }); + + test('#3162: resolve_model_ids must be in VALID_CONFIG_KEYS (documented user-facing key)', () => { + assert.ok( + VALID_CONFIG_KEYS.has('resolve_model_ids'), + 'resolve_model_ids is documented in CONFIGURATION.md and read by core.cjs/session-runner.ts' + ); + }); + + test('#3162: workflow._auto_chain_active must be accepted by isValidConfigKey (written by workflows)', () => { + assert.ok( + isValidConfigKey('workflow._auto_chain_active'), + 'workflow._auto_chain_active is written by plan-phase, execute-phase, discuss-phase, transition workflows via config-set' + ); + }); }); describe('CONFIG_KEY_SUGGESTIONS migration hints (#2535)', () => { From 9ae4426ebbc6c8fe8b321cba7a124708200869a8 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 6 May 2026 14:57:31 -0400 Subject: [PATCH 2/4] docs(changelog): add #3162 fixed entries for resolve_model_ids and workflow._auto_chain_active Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6754c6610..b5110f3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -309,6 +309,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). pre-existing sentinel force-removes the orphan worktree before starting fresh, making the agent self-healing across crashes. (#2839) +### Fixed + +- **`config-set resolve_model_ids` no longer rejected** — `resolve_model_ids` was + documented in CONFIGURATION.md and read by model-resolution paths, but missing from + the CJS/SDK `VALID_CONFIG_KEYS` allowlists. Added to both. (#3162) +- **`config-set workflow._auto_chain_active` no longer emits spurious errors** — this + internal runtime-state key is written by `plan-phase`, `execute-phase`, + `discuss-phase`, `transition`, and `new-project` workflows via `config-set`, but was + excluded from the public allowlist after #2530. A new `RUNTIME_STATE_KEYS` set lets + `isValidConfigKey()` accept it without exposing it as a user-settable option. (#3162) + ## [1.39.1] - 2026-05-01 From a1a81eec9049710230f45912f24bba19830e51cf Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 6 May 2026 15:19:34 -0400 Subject: [PATCH 3/4] fix(config): align SDK runtime-state key validation with CJS --- CHANGELOG.md | 2 -- sdk/src/query/config-mutation.ts | 3 +- sdk/src/query/config-schema.ts | 11 +++++++- tests/bug-2530-valid-config-keys.test.cjs | 3 +- tests/config-schema-sdk-parity.test.cjs | 34 +++++++++++++++++++---- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5110f3a5..9766aa24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -309,8 +309,6 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). pre-existing sentinel force-removes the orphan worktree before starting fresh, making the agent self-healing across crashes. (#2839) -### Fixed - - **`config-set resolve_model_ids` no longer rejected** — `resolve_model_ids` was documented in CONFIGURATION.md and read by model-resolution paths, but missing from the CJS/SDK `VALID_CONFIG_KEYS` allowlists. Added to both. (#3162) diff --git a/sdk/src/query/config-mutation.ts b/sdk/src/query/config-mutation.ts index 4ac28f7df..5ed243ed0 100644 --- a/sdk/src/query/config-mutation.ts +++ b/sdk/src/query/config-mutation.ts @@ -23,7 +23,7 @@ import { homedir } from 'node:os'; import { join } from 'node:path'; import { GSDError, ErrorClassification } from '../errors.js'; import { VALID_PROFILES, getAgentToModelMapForProfile } from './config-query.js'; -import { VALID_CONFIG_KEYS, DYNAMIC_KEY_PATTERNS } from './config-schema.js'; +import { VALID_CONFIG_KEYS, RUNTIME_STATE_KEYS, DYNAMIC_KEY_PATTERNS } from './config-schema.js'; import { planningPaths } from './helpers.js'; import { acquireStateLock, releaseStateLock } from './state-mutation.js'; import { maskIfSecret } from './secrets.js'; @@ -86,6 +86,7 @@ const CONFIG_KEY_SUGGESTIONS: Record = { */ export function isValidConfigKey(keyPath: string): { valid: boolean; suggestion?: string } { if (VALID_CONFIG_KEYS.has(keyPath)) return { valid: true }; + if (RUNTIME_STATE_KEYS.has(keyPath)) return { valid: true }; // Dynamic patterns — all sourced from shared config-schema (#2653). // Covers agent_skills.*, review.models.*, features.*, diff --git a/sdk/src/query/config-schema.ts b/sdk/src/query/config-schema.ts index d1b245f17..6788a9ab4 100644 --- a/sdk/src/query/config-schema.ts +++ b/sdk/src/query/config-schema.ts @@ -75,6 +75,14 @@ export const VALID_CONFIG_KEYS: ReadonlySet = new Set([ 'resolve_model_ids', ]); +/** + * Internal runtime-state keys accepted by config-set workflows but not exposed + * as user-facing config options. + */ +export const RUNTIME_STATE_KEYS: ReadonlySet = new Set([ + 'workflow._auto_chain_active', +]); + /** * Dynamic-pattern validators — keys matching these regexes are also accepted. * Each entry's `source` MUST equal the corresponding CJS regex `.source` @@ -127,8 +135,9 @@ export const DYNAMIC_KEY_PATTERNS: readonly DynamicKeyPattern[] = [ }, ]; -/** Returns true if keyPath is a valid config key (exact or dynamic pattern). */ +/** Returns true if keyPath is a valid config key (exact, runtime-state, or dynamic pattern). */ export function isValidConfigKeyPath(keyPath: string): boolean { if (VALID_CONFIG_KEYS.has(keyPath)) return true; + if (RUNTIME_STATE_KEYS.has(keyPath)) return true; return DYNAMIC_KEY_PATTERNS.some((p) => p.test(keyPath)); } diff --git a/tests/bug-2530-valid-config-keys.test.cjs b/tests/bug-2530-valid-config-keys.test.cjs index 257a94358..bef5e4814 100644 --- a/tests/bug-2530-valid-config-keys.test.cjs +++ b/tests/bug-2530-valid-config-keys.test.cjs @@ -55,8 +55,9 @@ describe('VALID_CONFIG_KEYS correctness', () => { }); test('#3162: workflow._auto_chain_active must be accepted by isValidConfigKey (written by workflows)', () => { - assert.ok( + assert.strictEqual( isValidConfigKey('workflow._auto_chain_active'), + true, 'workflow._auto_chain_active is written by plan-phase, execute-phase, discuss-phase, transition workflows via config-set' ); }); diff --git a/tests/config-schema-sdk-parity.test.cjs b/tests/config-schema-sdk-parity.test.cjs index 62aa98cfd..0741fd865 100644 --- a/tests/config-schema-sdk-parity.test.cjs +++ b/tests/config-schema-sdk-parity.test.cjs @@ -23,18 +23,22 @@ const fs = require('node:fs'); const path = require('node:path'); const ROOT = path.resolve(__dirname, '..'); -const { VALID_CONFIG_KEYS: CJS_KEYS, DYNAMIC_KEY_PATTERNS: CJS_PATTERNS } = +const { + VALID_CONFIG_KEYS: CJS_KEYS, + RUNTIME_STATE_KEYS: CJS_RUNTIME_KEYS, + DYNAMIC_KEY_PATTERNS: CJS_PATTERNS, +} = require('../get-shit-done/bin/lib/config-schema.cjs'); const SDK_SCHEMA_PATH = path.join(ROOT, 'sdk', 'src', 'query', 'config-schema.ts'); const SDK_SRC = fs.readFileSync(SDK_SCHEMA_PATH, 'utf8'); -function extractSdkKeys(src) { - const start = src.indexOf('VALID_CONFIG_KEYS'); - assert.ok(start > -1, 'SDK config-schema.ts must export VALID_CONFIG_KEYS'); +function extractSdkSet(src, setName) { + const start = src.indexOf(setName); + assert.ok(start > -1, `SDK config-schema.ts must export ${setName}`); const setOpen = src.indexOf('new Set([', start); const setClose = src.indexOf('])', setOpen); - assert.ok(setOpen > -1 && setClose > -1, 'VALID_CONFIG_KEYS must be a new Set([...]) literal'); + assert.ok(setOpen > -1 && setClose > -1, `${setName} must be a new Set([...]) literal`); const body = src.slice(setOpen + 'new Set(['.length, setClose); const keys = new Set(); for (const match of body.matchAll(/'([^']+)'/g)) keys.add(match[1]); @@ -52,7 +56,7 @@ function extractSdkPatternSources(src) { } test('#2653 — SDK VALID_CONFIG_KEYS matches CJS VALID_CONFIG_KEYS', () => { - const sdkKeys = extractSdkKeys(SDK_SRC); + const sdkKeys = extractSdkSet(SDK_SRC, 'VALID_CONFIG_KEYS'); const missingInSdk = [...CJS_KEYS].filter((k) => !sdkKeys.has(k)); const extraInSdk = [...sdkKeys].filter((k) => !CJS_KEYS.has(k)); assert.deepStrictEqual( @@ -69,6 +73,24 @@ test('#2653 — SDK VALID_CONFIG_KEYS matches CJS VALID_CONFIG_KEYS', () => { ); }); +test('#3162 — SDK RUNTIME_STATE_KEYS matches CJS RUNTIME_STATE_KEYS', () => { + const sdkRuntimeKeys = extractSdkSet(SDK_SRC, 'RUNTIME_STATE_KEYS'); + const missingInSdk = [...CJS_RUNTIME_KEYS].filter((k) => !sdkRuntimeKeys.has(k)); + const extraInSdk = [...sdkRuntimeKeys].filter((k) => !CJS_RUNTIME_KEYS.has(k)); + assert.deepStrictEqual( + missingInSdk, + [], + 'CJS runtime-state keys missing from sdk/src/query/config-schema.ts:\n' + + missingInSdk.map((k) => ' ' + k).join('\n'), + ); + assert.deepStrictEqual( + extraInSdk, + [], + 'SDK runtime-state keys missing from get-shit-done/bin/lib/config-schema.cjs:\n' + + extraInSdk.map((k) => ' ' + k).join('\n'), + ); +}); + test('#2653 — SDK DYNAMIC_KEY_PATTERNS sources match CJS regex .source', () => { const sdkSources = new Set(extractSdkPatternSources(SDK_SRC)); const cjsSources = CJS_PATTERNS.map((p) => { From b4894323e5385a8134df37a347ff4ff2a32180a3 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 6 May 2026 15:21:24 -0400 Subject: [PATCH 4/4] chore: add changeset fragment for #3188 --- .changeset/wise-mice-cheer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/wise-mice-cheer.md diff --git a/.changeset/wise-mice-cheer.md b/.changeset/wise-mice-cheer.md new file mode 100644 index 000000000..037635d73 --- /dev/null +++ b/.changeset/wise-mice-cheer.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3188 +--- +config-set resolve_model_ids no longer rejected with "Unknown config key"; workflow._auto_chain_active written by workflows no longer emits spurious key-validation errors. Fixes #3162.