fix(#3151): stop emitting effort: into skill frontmatter (cache invalidation) (#3425)

Claude Code applies SKILL.md effort: as output_config.effort; any change from
the session baseline invalidates the prompt cache at BOTH scope boundaries
(entry + exit, the latter often machine-fired via subagent-completion
notification). The reporter's owned measurement confirms it: /gsd-progress
(effort:low) in a medium session → cache_creation 63,404 (entry) + 18,589
(exit), while a no-effort skill shows none. ~76% of invocations paid in full.

Fix (trek-e AC#2/AC#4): convertClaudeCommandToClaudeSkill no longer emits
effort: into Claude-runtime skill frontmatter (src/runtime-artifact-conversion.cts
+ duplicated bin/install.js). normalizeClaudeSkillEffort removed (dead). The six
declaring skills (plan-phase/execute-phase/autonomous/next/progress/stats) no
longer carry effort. Source command files keep effort (input, used elsewhere);
the separate agent-effort surface (#3160) is untouched.

Tests: install-runtime-artifacts #769 block flipped to assert effort is ABSENT
from installed SKILL.md + converter output (the AC#4 behavioral coverage).

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-13 22:00:26 -04:00
committed by GitHub
parent d30c99bc92
commit b9adedbc86
13 changed files with 118 additions and 262 deletions

View File

@@ -392,16 +392,6 @@ function skillFrontmatterName(skillDirName) {
return skillDirName;
}
function normalizeClaudeSkillEffort(effort) {
// #3039: `max` is rejected by Anthropic models when extended thinking is
// disabled (400: output_config.effort 'max' is not supported when thinking
// is disabled). The frontmatter is static at install time and the installer
// cannot know whether thinking will be on or off at invocation. `high` is the
// maximum value that works in both states on all supported models.
if (effort === 'xhigh' || effort === 'max') return 'high';
return effort;
}
/**
* Qwen Code skills accept an optional numeric `priority` frontmatter field.
* Per the Qwen skills spec (qwen-code/docs/users/features/skills.md, verified
@@ -458,10 +448,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
const description = extractFrontmatterField(frontmatter, 'description') || '';
const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
const agent = extractFrontmatterField(frontmatter, 'agent');
// #769: preserve context: and effort: from source command files so they
// are emitted into the installed SKILL.md frontmatter unchanged.
// #769: preserve context: from source command files so it is emitted into
// the installed SKILL.md frontmatter unchanged. (#3151: effort: is no longer
// emitted into skill frontmatter — a static effort value changes
// output_config.effort on invocation and invalidates the caller's prompt
// cache at both scope boundaries; the reporter's owned measurement confirms
// the mechanism. The separate agent-effort surface is tracked by #3160.)
const context = extractFrontmatterField(frontmatter, 'context');
const effort = extractFrontmatterField(frontmatter, 'effort');
// Preserve allowed-tools as YAML multiline list (Claude native format)
const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
@@ -498,12 +491,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
}
if (argumentHint) fm += `argument-hint: ${yamlQuote(argumentHint)}\n`;
if (agent) fm += `agent: ${agent}\n`;
// #769: emit context: and effort: when present so the runtime can honour
// them natively (context: fork = isolated subagent window; effort: =
// token-budget tier). Fields are Claude-specific; unknown frontmatter
// fields are silently ignored by other runtimes (backward-compatible).
// #769: emit context: when present so the runtime can honour it natively
// (context: fork = isolated subagent window). Claude-specific; unknown
// frontmatter fields are silently ignored by other runtimes (backward-compatible).
// (#3151: effort: is intentionally NOT emitted into skill frontmatter — a
// static effort value changes output_config.effort on invocation and
// invalidates the caller's prompt cache at both scope boundaries.)
if (context) fm += `context: ${context}\n`;
if (effort) fm += `effort: ${normalizeClaudeSkillEffort(effort)}\n`;
if (toolsBlock) fm += toolsBlock;
fm += '---';