fix(#913): guard top-level Claude Code plan-phase against role collapse (#915)

Three-part fix for the top-level inline collapse bug:

1. plan-phase.md: add <runtime_compatibility> block after
   </available_agent_types> that makes the Agent-availability
   requirement explicit; workflow fails-closed (stops with a clear
   log) in genuinely Agent-less contexts.

2. plan-phase.md: rename 7 "ORCHESTRATOR RULE — CODEX RUNTIME"
   labels to "ALL RUNTIMES" so the spawn guard applies universally
   (not just when Codex is detected).

3. execute-phase.md: scope the existing "Other runtimes" inline-
   fallback prose to non-Claude contexts, preserving the #853
   backgrounded-agent behaviour for Claude Code background agents.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 5bf77a527f)
This commit is contained in:
Tom Boucher
2026-06-08 23:32:34 -04:00
committed by github-actions[bot]
parent e0f67c75cd
commit bb27263def
5 changed files with 104 additions and 13 deletions

View File

@@ -135,3 +135,61 @@ describe('plan-phase workflow: Artifacts this phase produces section (#22)', ()
);
});
});
// ─── (C) Top-level spawn guard (#913) ────────────────────────────────────────
describe('plan-phase workflow: top-level spawn guard (#913)', () => {
// Extract the runtime_compatibility block for targeted assertions
const rtBlock = (() => {
const m = workflow.match(/<runtime_compatibility>([\s\S]*?)<\/runtime_compatibility>/);
return m ? m[1] : '';
})();
test('workflow has a runtime_compatibility block asserting Agent is available at top-level', () => {
assert.ok(
rtBlock.length > 0,
'plan-phase must have a <runtime_compatibility> block — prevents role-collapse regression (#913)'
);
assert.ok(
rtBlock.includes('Agent tool IS available') || rtBlock.includes('Agent IS available'),
'plan-phase runtime_compatibility must assert that the Agent tool IS available at top-level Claude Code (#913)'
);
assert.ok(
rtBlock.toLowerCase().includes('top-level'),
'plan-phase runtime_compatibility must scope the IS-available assertion to top-level Claude Code (#913)'
);
assert.ok(
rtBlock.includes('Always spawn') || rtBlock.includes('always spawn'),
'plan-phase runtime_compatibility must state that plan roles must always be spawned (#913)'
);
assert.ok(
rtBlock.includes('Never absorb') || rtBlock.includes('never absorb'),
'plan-phase runtime_compatibility must state that roles must never be absorbed inline (#913)'
);
});
test('workflow states --chain/--auto suppress prompts only, not spawns', () => {
assert.ok(
rtBlock.includes('suppress') &&
(rtBlock.includes('prompts only') || rtBlock.includes('interactive prompts only')),
'plan-phase runtime_compatibility must document that --chain/--auto suppress prompts only, not spawns (#913)'
);
});
test('workflow does not contain unscoped CODEX RUNTIME orchestrator rule labels', () => {
// All "wait for subagent" rules must apply to ALL RUNTIMES, not just Codex
assert.ok(
!workflow.includes('ORCHESTRATOR RULE — CODEX RUNTIME'),
'plan-phase must not label orchestrator wait rules as "CODEX RUNTIME" — they apply to all runtimes including top-level Claude Code (#913)'
);
});
test('workflow contains ALL RUNTIMES orchestrator rule labels (count preserved)', () => {
// Must have all 7 agent-spawn wait rules still present (none dropped during rename)
const allRuntimesCount = (workflow.match(/ORCHESTRATOR RULE — ALL RUNTIMES/g) || []).length;
assert.ok(
allRuntimesCount >= 7,
`plan-phase must have at least 7 "ORCHESTRATOR RULE — ALL RUNTIMES" labels (one per agent spawn site); found ${allRuntimesCount} (#913)`
);
});
});

View File

@@ -81,8 +81,8 @@ const GRACE = 3000;
// current high-water mark within GRACE (#597 tighten-only ratchet).
// XL high-water mark is execute-phase.md — note that under LINES it was
// plan-phase; bytes genuinely re-rank the tier, which is the point of #717.
// actualMax=91161 (execute-phase, #891 launcher shim expansion — added 17 runtime home arms);
// slack=1839 ≤ GRACE. plan-phase.md=88120, new-project.md=58110; both well under ceiling.
// actualMax=92525 (execute-phase, #913 inline-fallback scope clarification);
// slack=475 ≤ GRACE. plan-phase.md=90501 (#913 runtime_compatibility block + label rename), new-project.md=58110.
const XL_BUDGET = 93000;
// LARGE high-water mark is docs-update.md. actualMax=54410 (#891 launcher shim expansion);
// slack=1590 ≤ GRACE. quick.md=45710, autonomous.md=38030.
@@ -95,8 +95,8 @@ const DEFAULT_BUDGET = 40000;
// Grandfathered at current sizes — see PR #2551 for the progressive-disclosure
// pattern that future shrinks should follow. Byte counts noted for reference.
const XL_WORKFLOWS = new Set([
'execute-phase', // 91161 bytes (tier high-water mark; grew in #891 launcher shim expansion)
'plan-phase', // 85068 bytes
'execute-phase', // 92525 bytes (tier high-water mark; grew in #913 inline-fallback scope clarification)
'plan-phase', // 90501 bytes (grew in #913 runtime_compatibility block + label rename)
'new-project', // 55850 bytes
]);