From d1fe1c0cd292d863bc23723edfd61a54b30b425c Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 15 Aug 2026 03:17:57 -0400 Subject: [PATCH 1/4] test(#3534): failing-first suite for resolve-execution effective effort --- tests/effort-surface-axis.test.cjs | 93 ++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/effort-surface-axis.test.cjs b/tests/effort-surface-axis.test.cjs index 76ab48e80..3903531ae 100644 --- a/tests/effort-surface-axis.test.cjs +++ b/tests/effort-surface-axis.test.cjs @@ -91,6 +91,99 @@ function projectWithEffort(effort) { return dir; } +describe('#3534 resolve-execution reports resolved AND effective effort', () => { + function agentHome(t, agentFileBody) { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-3534-home-')); + t.after(() => cleanup(home)); + if (agentFileBody !== null) { + fs.mkdirSync(path.join(home, 'agents'), { recursive: true }); + fs.writeFileSync(path.join(home, 'agents', 'gsd-executor.md'), agentFileBody); + } + return home; + } + + function resolveExecution(dir, agent = 'gsd-executor', extra = []) { + return JSON.parse( + runGsdTools(`query resolve-execution ${agent} ${extra.join(' ')}`, dir).output, + ); + } + + test('10a: effective effort reads the installed frontmatter (claude)', (t) => { + const dir = projectWithEffort('high'); + t.after(() => cleanup(dir)); + const home = agentHome(t, '---\nname: gsd-executor\neffort: low\ndescription: x\n---\nBody.\n'); + const out = resolveExecutionWithClaudeHome(dir, home); + assert.equal(out.effort, 'high', 'resolved cascade value unchanged'); + assert.equal(out.effort_effective, 'low', 'the installed frontmatter value'); + assert.equal(out.effort_effective_source, 'frontmatter'); + // Existing keys all still present, unchanged shape. + for (const k of ['model', 'profile', 'effort', 'effort_rendered', 'effort_param', 'effort_propagation', 'fast_mode', 'fast_mode_supported']) { + assert.ok(k in out, `existing key ${k} must remain`); + } + }); + + test('10a: absent frontmatter reports inherit as the effective state (the 10a repro)', (t) => { + const dir = projectWithEffort('high'); + t.after(() => cleanup(dir)); + const home = agentHome(t, '---\nname: gsd-executor\ndescription: x\n---\nBody.\n'); + const out = resolveExecutionWithClaudeHome(dir, home); + assert.equal(out.effort, 'high'); + assert.equal(out.effort_effective, 'inherit', 'absent key = follows the session'); + assert.equal(out.effort_effective_source, 'frontmatter-absent'); + }); + + test('10a: missing agent file falls back to resolved with the flag', (t) => { + const dir = projectWithEffort('high'); + t.after(() => cleanup(dir)); + const home = agentHome(t, null); + const out = resolveExecutionWithClaudeHome(dir, home); + assert.equal(out.effort_effective, out.effort); + assert.equal(out.effort_effective_source, 'resolved'); + }); + + test('10a: runtimes without an install-time channel report resolved', (t) => { + const dir = createTempProject(); + t.after(() => cleanup(dir)); + fs.writeFileSync( + path.join(dir, '.planning', 'config.json'), + JSON.stringify({ runtime: 'codex', effort: { default: 'medium' } }, null, 2), + ); + const out = resolveExecution(dir); + assert.equal(out.effort, 'medium'); + assert.equal(out.effort_effective, 'medium'); + assert.equal(out.effort_effective_source, 'resolved'); + }); + + test('10a: CRLF frontmatter is read', (t) => { + const dir = projectWithEffort('high'); + t.after(() => cleanup(dir)); + const home = agentHome(t, ['---', 'name: gsd-executor', 'effort: xhigh', 'description: x', '---', 'Body.', ''].join('\r\n')); + const out = resolveExecutionWithClaudeHome(dir, home); + assert.equal(out.effort_effective, 'xhigh'); + assert.equal(out.effort_effective_source, 'frontmatter'); + }); + + test('10a: frontmatter-less agent file degrades to resolved', (t) => { + const dir = projectWithEffort('high'); + t.after(() => cleanup(dir)); + const home = agentHome(t, 'No frontmatter here at all.\n'); + const out = resolveExecutionWithClaudeHome(dir, home); + assert.equal(out.effort_effective, out.effort); + assert.equal(out.effort_effective_source, 'resolved'); + }); + + function resolveExecutionWithClaudeHome(dir, home) { + const prev = process.env.CLAUDE_CONFIG_DIR; + process.env.CLAUDE_CONFIG_DIR = home; + try { + return resolveExecution(dir); + } finally { + if (prev === undefined) delete process.env.CLAUDE_CONFIG_DIR; + else process.env.CLAUDE_CONFIG_DIR = prev; + } + } +}); + describe('#2481 effortSurface — closed vocabulary', () => { test('is exactly argv|none — no config-file member', () => { // Gemini CLI was the only host with a config-file effort surface and was From d26bfc2a3fce8e5d24ee6bfe6b5ea8bc3eaccfa1 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 15 Aug 2026 03:19:18 -0400 Subject: [PATCH 2/4] fix(#3534): resolve-execution reports resolved and effective effort --- .changeset/bold-badgers-climb.md | 5 +++++ docs/CONFIGURATION.md | 8 ++++++++ src/commands.cts | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 .changeset/bold-badgers-climb.md diff --git a/.changeset/bold-badgers-climb.md b/.changeset/bold-badgers-climb.md new file mode 100644 index 000000000..7ebba6cb1 --- /dev/null +++ b/.changeset/bold-badgers-climb.md @@ -0,0 +1,5 @@ +--- +type: Added +pr: 0 +--- +**`resolve-execution` now tells the truth about what the agent will run at** — the query reported only the config-cascade effort, which is not what an installed agent uses when its `effort:` frontmatter was hand-stripped or drifted. `--json` adds `effort_effective` (read from the installed agent frontmatter for the claude runtime; `"inherit"` when the key is absent) and `effort_effective_source` (`frontmatter` | `frontmatter-absent` | `resolved`). All existing fields, including `--pick effort`, are unchanged. (#3534) diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index a3bc1cd34..cbaf11ac1 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1416,6 +1416,14 @@ The model-catalog's `reasoning_effort` per-tier hint is a legacy field kept for Valid effort values: `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. +`query resolve-execution --json` reports two effort views ([#3534](https://github.com/open-gsd/gsd-core/issues/3534)): +`effort` is the **resolved** config-cascade value; `effort_effective` is what the installed +agent will actually run at — read from the installed agent's `effort:` frontmatter for the +claude runtime (`effort_effective_source: "frontmatter"`), reported as `"inherit"` when the +key is absent (`"frontmatter-absent"` — the agent follows the session effort), and equal to +the resolved value with source `"resolved"` when there is no install-time channel or no +agent file to read. `--pick effort` still returns the resolved value. + #### Where effort actually reaches — added in v1.8.0 Effort resolved from the cascade above reaches a runtime through one of two channels. diff --git a/src/commands.cts b/src/commands.cts index a3fee9521..cc54fa41c 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -620,6 +620,38 @@ function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: bo const fastModeSupported = RUNTIMES_WITH_FAST_MODE.has(runtime); + // #3534 (10a): the effective effort — what the installed agent will actually + // run at. `effort` above is the config cascade; for the claude runtime the + // per-agent frontmatter key is the source of truth (Claude Code's Agent tool + // has no per-spawn effort parameter), so the query reads the installed file. + // An ABSENT key is a real state — the agent follows the session effort + // ('inherit'), not drift. No file / no frontmatter / any read failure means + // no evidence: the resolved value is reported, flagged 'resolved' so a + // consumer can tell evidence from echo. Additive only — every existing key + // is unchanged. + let effortEffectiveSource: 'frontmatter' | 'frontmatter-absent' | 'resolved' = 'resolved'; + let effortEffective: string = effort; + if (runtime === 'claude') { + try { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method + const { getGlobalConfigDir } = require('./runtime-homes.cjs') as { getGlobalConfigDir(runtime: string, explicitDir?: string | null): string }; + const agentPath = path.join(getGlobalConfigDir(runtime), 'agents', `${agentType}.md`); + const agentContent = fs.readFileSync(agentPath, 'utf8'); + // eslint-disable-next-line local/no-unbounded-quantifier -- same lazy `*?` bounded by the `^---$/m` closing anchor as the sibling frontmatter regexes in this file + const fmMatchEff = /^---\r?\n([\s\S]*?)^---\r?$/m.exec(agentContent); + if (fmMatchEff) { + const effortLine = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatchEff[1]); + if (effortLine) { + effortEffective = effortLine[1]; + effortEffectiveSource = 'frontmatter'; + } else { + effortEffective = 'inherit'; + effortEffectiveSource = 'frontmatter-absent'; + } + } + } catch { /* no frontmatter evidence — stay on the resolved value */ } + } + // Own-property guard: agentType is an unvalidated CLI positional, so a // prototype-chain value ("toString", "constructor") would otherwise return // an inherited truthy member from this plain object and misreport a @@ -633,6 +665,8 @@ function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: bo effort_rendered: rendered.value, effort_param: rendered.param, effort_propagation: rendered.channel, + effort_effective: effortEffective, + effort_effective_source: effortEffectiveSource, fast_mode: fastMode, fast_mode_supported: fastModeSupported, }; From ace777dd563863bf1d8b71bb881f8bc7ab8d2558 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 15 Aug 2026 03:35:10 -0400 Subject: [PATCH 3/4] fix(#3534): hermetic child env for fixture home; contain agent read to agents dir --- src/commands.cts | 9 ++++++++- tests/effort-surface-axis.test.cjs | 27 ++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/commands.cts b/src/commands.cts index cc54fa41c..41e4bf13c 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -635,7 +635,14 @@ function cmdResolveExecution(cwd: string, agentType: string | undefined, raw: bo try { // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method const { getGlobalConfigDir } = require('./runtime-homes.cjs') as { getGlobalConfigDir(runtime: string, explicitDir?: string | null): string }; - const agentPath = path.join(getGlobalConfigDir(runtime), 'agents', `${agentType}.md`); + const agentsDirEff = path.join(getGlobalConfigDir(runtime), 'agents'); + const agentPath = path.join(agentsDirEff, `${agentType}.md`); + // agentType is an unvalidated CLI positional: keep the read inside the + // agents dir so `../../x` cannot point it elsewhere (defense in depth — + // the reflected surface is only a frontmatter effort line). + if (!path.resolve(agentPath).startsWith(path.resolve(agentsDirEff) + path.sep)) { + throw new Error('agent path escapes the agents directory'); + } const agentContent = fs.readFileSync(agentPath, 'utf8'); // eslint-disable-next-line local/no-unbounded-quantifier -- same lazy `*?` bounded by the `^---$/m` closing anchor as the sibling frontmatter regexes in this file const fmMatchEff = /^---\r?\n([\s\S]*?)^---\r?$/m.exec(agentContent); diff --git a/tests/effort-surface-axis.test.cjs b/tests/effort-surface-axis.test.cjs index 3903531ae..938dfbb5f 100644 --- a/tests/effort-surface-axis.test.cjs +++ b/tests/effort-surface-axis.test.cjs @@ -102,9 +102,9 @@ describe('#3534 resolve-execution reports resolved AND effective effort', () => return home; } - function resolveExecution(dir, agent = 'gsd-executor', extra = []) { + function resolveExecution(dir, agent = 'gsd-executor', extra = [], env = {}) { return JSON.parse( - runGsdTools(`query resolve-execution ${agent} ${extra.join(' ')}`, dir).output, + runGsdTools(`query resolve-execution ${agent} ${extra.join(' ')}`, dir, env).output, ); } @@ -112,7 +112,10 @@ describe('#3534 resolve-execution reports resolved AND effective effort', () => const dir = projectWithEffort('high'); t.after(() => cleanup(dir)); const home = agentHome(t, '---\nname: gsd-executor\neffort: low\ndescription: x\n---\nBody.\n'); - const out = resolveExecutionWithClaudeHome(dir, home); + // #3534: pass the fixture home as the CHILD env argument — testEnvBase() + // blanks CLAUDE_CONFIG_DIR after the process.env spread, so a process.env + // mutation never reaches the child (and a dev's real ~/.claude would). + const out = resolveExecution(dir, 'gsd-executor', [], { CLAUDE_CONFIG_DIR: home }); assert.equal(out.effort, 'high', 'resolved cascade value unchanged'); assert.equal(out.effort_effective, 'low', 'the installed frontmatter value'); assert.equal(out.effort_effective_source, 'frontmatter'); @@ -126,7 +129,7 @@ describe('#3534 resolve-execution reports resolved AND effective effort', () => const dir = projectWithEffort('high'); t.after(() => cleanup(dir)); const home = agentHome(t, '---\nname: gsd-executor\ndescription: x\n---\nBody.\n'); - const out = resolveExecutionWithClaudeHome(dir, home); + const out = resolveExecution(dir, 'gsd-executor', [], { CLAUDE_CONFIG_DIR: home }); assert.equal(out.effort, 'high'); assert.equal(out.effort_effective, 'inherit', 'absent key = follows the session'); assert.equal(out.effort_effective_source, 'frontmatter-absent'); @@ -136,7 +139,7 @@ describe('#3534 resolve-execution reports resolved AND effective effort', () => const dir = projectWithEffort('high'); t.after(() => cleanup(dir)); const home = agentHome(t, null); - const out = resolveExecutionWithClaudeHome(dir, home); + const out = resolveExecution(dir, 'gsd-executor', [], { CLAUDE_CONFIG_DIR: home }); assert.equal(out.effort_effective, out.effort); assert.equal(out.effort_effective_source, 'resolved'); }); @@ -158,7 +161,7 @@ describe('#3534 resolve-execution reports resolved AND effective effort', () => const dir = projectWithEffort('high'); t.after(() => cleanup(dir)); const home = agentHome(t, ['---', 'name: gsd-executor', 'effort: xhigh', 'description: x', '---', 'Body.', ''].join('\r\n')); - const out = resolveExecutionWithClaudeHome(dir, home); + const out = resolveExecution(dir, 'gsd-executor', [], { CLAUDE_CONFIG_DIR: home }); assert.equal(out.effort_effective, 'xhigh'); assert.equal(out.effort_effective_source, 'frontmatter'); }); @@ -167,21 +170,11 @@ describe('#3534 resolve-execution reports resolved AND effective effort', () => const dir = projectWithEffort('high'); t.after(() => cleanup(dir)); const home = agentHome(t, 'No frontmatter here at all.\n'); - const out = resolveExecutionWithClaudeHome(dir, home); + const out = resolveExecution(dir, 'gsd-executor', [], { CLAUDE_CONFIG_DIR: home }); assert.equal(out.effort_effective, out.effort); assert.equal(out.effort_effective_source, 'resolved'); }); - function resolveExecutionWithClaudeHome(dir, home) { - const prev = process.env.CLAUDE_CONFIG_DIR; - process.env.CLAUDE_CONFIG_DIR = home; - try { - return resolveExecution(dir); - } finally { - if (prev === undefined) delete process.env.CLAUDE_CONFIG_DIR; - else process.env.CLAUDE_CONFIG_DIR = prev; - } - } }); describe('#2481 effortSurface — closed vocabulary', () => { From 7d33ab457942e7967b33dbcd0b523a321906b1a7 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 15 Aug 2026 03:46:01 -0400 Subject: [PATCH 4/4] chore(#3534): backfill changeset pr number --- .changeset/bold-badgers-climb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/bold-badgers-climb.md b/.changeset/bold-badgers-climb.md index 7ebba6cb1..b81c96f9e 100644 --- a/.changeset/bold-badgers-climb.md +++ b/.changeset/bold-badgers-climb.md @@ -1,5 +1,5 @@ --- type: Added -pr: 0 +pr: 3542 --- **`resolve-execution` now tells the truth about what the agent will run at** — the query reported only the config-cascade effort, which is not what an installed agent uses when its `effort:` frontmatter was hand-stripped or drifted. `--json` adds `effort_effective` (read from the installed agent frontmatter for the claude runtime; `"inherit"` when the key is absent) and `effort_effective_source` (`frontmatter` | `frontmatter-absent` | `resolved`). All existing fields, including `--pick effort`, are unchanged. (#3534)