* fix(#3324): build-time embed execution context instead of literal @-includes * chore(#3324): add changeset * chore(#3324): set changeset pr reference * fix(#3324): trim embed note to stay under the 93400 margin ceiling --------- Co-authored-by: sim <sim@local>
This commit is contained in:
5
.changeset/serene-ibex-hum.md
Normal file
5
.changeset/serene-ibex-hum.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3462
|
||||
---
|
||||
Executor dispatch prompts no longer list companion files as raw @-include lines that Claude Code never expands inside an Agent() prompt string. The orchestrator now build-time embeds execute-plan.md and its companion references (summary template, checkpoints, tdd, worktree-path-safety, executor-examples) into the dispatched gsd-executor prompt, so execute-plan-only steps (segment_execution, previous_phase_check, verification_failure_gate, update_codebase_map) actually reach executors instead of silently never running.
|
||||
@@ -733,12 +733,13 @@ increases monotonically across waves. `{status}` is `complete` (success),
|
||||
</parallel_execution>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||
@~/.claude/gsd-core/templates/summary.md
|
||||
@~/.claude/gsd-core/references/checkpoints.md
|
||||
@~/.claude/gsd-core/references/tdd.md
|
||||
@~/.claude/gsd-core/references/worktree-path-safety.md
|
||||
${CONTEXT_WINDOW < 200000 ? '' : '@~/.claude/gsd-core/references/executor-examples.md'}
|
||||
ORCHESTRATOR build-time embed (NOT a sub-agent runtime step): before this dispatch, read each file listed below and replace this note with those files' contents, inlined verbatim in this block in the listed order. Never leave `@`-include lines in the dispatched prompt — `@path` never expands inside an Agent() `prompt="..."` string (#3324), so an include arrives as literal text the executor never sees.
|
||||
- `~/.claude/gsd-core/workflows/execute-plan.md`
|
||||
- `~/.claude/gsd-core/templates/summary.md`
|
||||
- `~/.claude/gsd-core/references/checkpoints.md`
|
||||
- `~/.claude/gsd-core/references/tdd.md`
|
||||
- `~/.claude/gsd-core/references/worktree-path-safety.md`
|
||||
${CONTEXT_WINDOW < 200000 ? '' : '- `~/.claude/gsd-core/references/executor-examples.md`'}
|
||||
</execution_context>
|
||||
|
||||
<files_to_read>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"paths": {
|
||||
"execute-phase.md": "#1689: per-plan agent_hint routing adds a one-line per-plan fragment reference and swaps the gsd-executor literal for a {EXECUTOR_TYPE} placeholder. Net +6 bytes, still under the ADR-857 Phase 6 byte ceiling (93600); all resolution detail lives in the execute-phase/steps/per-plan-executor-routing.md fragment to keep the host lean."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"paths": {
|
||||
"execute-phase.md": "#3324: the <execution_context> block of the gsd-executor Agent() dispatch prompt listed companion files as raw @~/ include lines. Claude Code expands @path only in natively-loaded markdown bodies, never inside a dynamically constructed prompt=\"...\" string, so every execute-plan.md-only step (segment_execution, previous_phase_check, verification_failure_gate, update_codebase_map) silently never reached dispatched executors. The block now carries the ORCHESTRATOR build-time embed instruction already established by the adjacent <worktree_branch_check> block (b88d6d6ef / #589) — the orchestrator reads each listed file and inlines its contents verbatim before calling Agent() — and the paths are backticked list entries with no @ sigil so no literal include line can leak into the prompt. Both dispatch paths share this block (sequential reuses the worktree-mode structure), and a repo-wide prompt-region guard in tests/workflow-size-budget.test.cjs prevents reintroduction anywhere under gsd-core/workflows/**. The 432-byte growth (92954 -> 93386, still under the XL 96 KiB ceiling) is the embed instruction line itself plus the backticked path list replacing the five bare include lines; every byte of it exists to prevent the silent step-loss failure mode from returning."
|
||||
}
|
||||
}
|
||||
@@ -602,3 +602,90 @@ describe('SIZE: byteCount is line-ending independent (#683 regression)', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #3324 — @-include lines inside Agent() prompt strings never expand
|
||||
// ---------------------------------------------------------------------------
|
||||
// Claude Code expands @path only in natively-loaded markdown bodies (CLAUDE.md,
|
||||
// slash-command/skill bodies, agent definitions) — never inside the prompt
|
||||
// parameter of a dynamically constructed Agent() call, which is delivered as
|
||||
// literal turn text. A bare @-include line in a prompt string means the
|
||||
// subagent never sees the referenced file (#3324).
|
||||
describe('#3324: no @-include lines inside Agent() prompt strings', () => {
|
||||
const BARE_INCLUDE_LINE = /^\s*@(\$HOME|~)\//;
|
||||
|
||||
function listWorkflowFilesRecursive(dir, out = []) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) listWorkflowFilesRecursive(p, out);
|
||||
else if (entry.name.endsWith('.md')) out.push(p);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// A prompt region opens at a line ending in `prompt="` and closes at the
|
||||
// first subsequent line that is only whitespace + a double quote.
|
||||
function bareIncludeLinesInPromptRegions(content) {
|
||||
const hits = [];
|
||||
let inPrompt = false;
|
||||
content.split('\n').forEach((line, i) => {
|
||||
if (!inPrompt && /prompt="\s*$/.test(line)) { inPrompt = true; return; }
|
||||
if (inPrompt && /^\s*"\s*$/.test(line)) { inPrompt = false; return; }
|
||||
if (inPrompt && BARE_INCLUDE_LINE.test(line)) {
|
||||
hits.push({ line: i + 1, text: line.trim() });
|
||||
}
|
||||
});
|
||||
return hits;
|
||||
}
|
||||
|
||||
test('no workflow prompt string contains a bare @-include line (repo-wide guard)', () => {
|
||||
const offenders = [];
|
||||
for (const file of listWorkflowFilesRecursive(WORKFLOWS_DIR)) {
|
||||
const rel = path.relative(path.join(__dirname, '..'), file);
|
||||
for (const hit of bareIncludeLinesInPromptRegions(fs.readFileSync(file, 'utf-8'))) {
|
||||
offenders.push(`${rel}:${hit.line} ${hit.text}`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(
|
||||
offenders,
|
||||
[],
|
||||
'Claude Code never expands @path inside a dynamically built Agent() ' +
|
||||
'prompt="..." string — the include arrives as literal text and the ' +
|
||||
'subagent never sees the referenced file. Use the ORCHESTRATOR ' +
|
||||
'build-time embed pattern (see execute-phase.md <worktree_branch_check> ' +
|
||||
'and <execution_context>) or inline the content. See #3324.'
|
||||
);
|
||||
});
|
||||
|
||||
test('execute-phase.md <execution_context> build-time embeds execute-plan.md instead of @-including it', () => {
|
||||
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'execute-phase.md'), 'utf-8');
|
||||
const block = content.match(/<execution_context>([\s\S]{0,4000}?)<\/execution_context>/);
|
||||
assert.ok(
|
||||
block,
|
||||
'execute-phase.md must keep an <execution_context> block in the executor dispatch prompt'
|
||||
);
|
||||
assert.ok(
|
||||
/ORCHESTRATOR build-time embed/.test(block[1]),
|
||||
'<execution_context> must carry the ORCHESTRATOR build-time embed instruction (#3324)'
|
||||
);
|
||||
assert.ok(
|
||||
/`~\/\.claude\/gsd-core\/workflows\/execute-plan\.md`/.test(block[1]),
|
||||
'<execution_context> must list execute-plan.md (backticked, no @ sigil) for build-time embed (#3324)'
|
||||
);
|
||||
});
|
||||
|
||||
test('execute-plan.md still defines the steps only it carries into the dispatch', () => {
|
||||
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'execute-plan.md'), 'utf-8');
|
||||
for (const marker of [
|
||||
'segment_execution',
|
||||
'previous_phase_check',
|
||||
'verification_failure_gate',
|
||||
'update_codebase_map',
|
||||
]) {
|
||||
assert.ok(
|
||||
content.includes(marker),
|
||||
`execute-plan.md must still define ${marker} — it reaches executors only via the build-time embed (#3324)`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user