mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-12 11:40:51 +01:00
fix(autoplan): the publication no-op is 'true autoplan-published <phase>'
Measured on Claude Code 2.1.292 in a default-settings interactive session:
a bare ':' Bash command asks for approval ('This command requires
approval'), and after a background reviewer's completion notice starts a
new turn the skill's allowed tools no longer cover it, so the phase-close
no-op prompted the user. 'true' with arguments runs without approval and
prints nothing. The guard only needs a later journaled record, so the
command changes everywhere it is named.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e811aaf4c0
commit
66f50f1b74
9 files changed
+13
-12
No files matched your search
@@ -19,7 +19,7 @@ interface Reason { disposition: Disposition; text: (d: Detail) => string }
|
||||
export const FALLBACK = 'Fallback: run /plan-ceo-review, then /plan-devex-review, then /plan-eng-review by hand; ' +
|
||||
'or run /context-save, start a new Claude Code session (not --resume), run /context-restore, then /autoplan <plan path>.';
|
||||
const PUBLISH_SEPARATELY = (d: Detail) => `Publish the filled Phase ${d.phase} report as your own parent assistant text in a message ` +
|
||||
`whose only tool call is the Bash no-op \`: autoplan-published <phase>\`, then make this phase-entry call in a later message.`;
|
||||
`whose only tool call is the Bash no-op \`true autoplan-published <phase>\`, then make this phase-entry call in a later message.`;
|
||||
|
||||
export const REASONS = {
|
||||
// Journal ownership (lib/claude-public-transcript.ts OwnedTranscriptReason).
|
||||
|
||||
@@ -651,7 +651,7 @@ function unverifiedOutput(code: string, detail: Detail, version: string | undefi
|
||||
const notice = reasonText(code, detail, version);
|
||||
return { systemMessage: `[autoplan] ${notice}`, hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext:
|
||||
`[autoplan] ${notice} This call proceeds unverified. Publish each completed phase report as your own parent ` +
|
||||
'assistant text, in a message whose only tool call is the Bash no-op `: autoplan-published <phase>`, before entering the next phase.' } };
|
||||
'assistant text, in a message whose only tool call is the Bash no-op `true autoplan-published <phase>`, before entering the next phase.' } };
|
||||
}
|
||||
|
||||
function decided(decision: Decision, via: EvaluationPath, version?: string): object {
|
||||
|
||||
@@ -42,7 +42,7 @@ preparation does not perform them.
|
||||
6. **Publish the parent report.** After successful verification, SEND the filled
|
||||
report below now as visible parent assistant text, using actual findings and voice
|
||||
statuses, in its own message whose only tool call is the Bash no-op
|
||||
`: autoplan-published <PHASE>` (no output). The guard counts a report only when a
|
||||
`true autoplan-published <PHASE>` (no output). The guard counts a report only when a
|
||||
later record follows it, so this message is the next operation before any next-phase tool call.
|
||||
Use the packet's `report` fields for this phase, the actual host's reviewer names,
|
||||
and N/A when either review voice is missing; confirmed counts require both voices.
|
||||
|
||||
@@ -40,7 +40,7 @@ preparation does not perform them.
|
||||
6. **Publish the parent report.** After successful verification, SEND the filled
|
||||
report below now as visible parent assistant text, using actual findings and voice
|
||||
statuses, in its own message whose only tool call is the Bash no-op
|
||||
`: autoplan-published <PHASE>` (no output). The guard counts a report only when a
|
||||
`true autoplan-published <PHASE>` (no output). The guard counts a report only when a
|
||||
later record follows it, so this message is the next operation before any next-phase tool call.
|
||||
Use the packet's `report` fields for this phase, the actual host's reviewer names,
|
||||
and N/A when either review voice is missing; confirmed counts require both voices.
|
||||
|
||||
@@ -38,8 +38,9 @@ to the hook. So the guard counts a phase report only when a later journaled
|
||||
record follows it: a tool result, or a record from another assistant message.
|
||||
|
||||
The phase-close procedure therefore publishes each report in a message whose
|
||||
only tool call is the Bash no-op `: autoplan-published <phase>` (it prints
|
||||
nothing and needs no permission), and makes the next phase's `Read` or `Agent`
|
||||
only tool call is the Bash no-op `true autoplan-published <phase>` (it prints
|
||||
nothing and needs no permission; Claude Code 2.1.292 asks for approval of a bare
|
||||
`:` command, so the no-op is `true`), and makes the next phase's `Read` or `Agent`
|
||||
call in a later message. Sessions that put the report and the next phase's
|
||||
call in one message get one [`publication_unflushed`](#publication-unflushed)
|
||||
denial naming that fix.
|
||||
@@ -191,7 +192,7 @@ be verified, so Claude Code is not writing it to the journal in time.
|
||||
### `publication_missing`
|
||||
No filled report for the previous phase appears after its close packet Read.
|
||||
Publish it as your own assistant text in a message whose only tool call is
|
||||
`: autoplan-published <phase>`, then enter the next phase in a later message.
|
||||
`true autoplan-published <phase>`, then enter the next phase in a later message.
|
||||
|
||||
<a id="publication-unflushed"></a>
|
||||
### `publication_unflushed`
|
||||
|
||||
@@ -356,7 +356,7 @@ describe('Autoplan parent publication guard', () => {
|
||||
expect(warning).toContain('code too_large');
|
||||
expect(warning).toContain('docs/autoplan-guard-troubleshooting.md#too-large');
|
||||
expect(warning).toContain('Phase-report enforcement was skipped');
|
||||
expect(output.hookSpecificOutput.additionalContext).toContain(': autoplan-published <phase>');
|
||||
expect(output.hookSpecificOutput.additionalContext).toContain('true autoplan-published <phase>');
|
||||
});
|
||||
|
||||
test('#3050: the batch read and the narrowed parent loop report too_large, not a plain error', async () => {
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('Cause B: the current call is not journaled at hook time (payload path)
|
||||
expect(codeOf(await f.hook(f.input('b', 'Agent', dispatch(f, { model: 'haiku' }))))).toBe('agent_key');
|
||||
const next = await f.hook(f.input('c', 'Read', { file_path: section('design-phase.md') }));
|
||||
expect(codeOf(next)).toBe('publication_missing');
|
||||
expect(denial(next)).toContain(': autoplan-published <phase>');
|
||||
expect(denial(next)).toContain('true autoplan-published <phase>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -121,7 +121,7 @@ describe('Publication flush: a report counts only once a later journaled record
|
||||
const f = make(); f.report(f.nextMessage()); f.journal();
|
||||
const output = await f.hook(next(f));
|
||||
expect(codeOf(output)).toBe('publication_unflushed');
|
||||
expect(denial(output)).toContain('`: autoplan-published <phase>`');
|
||||
expect(denial(output)).toContain('`true autoplan-published <phase>`');
|
||||
expect(denial(output)).not.toMatch(/\bretry\b/i);
|
||||
});
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export function guardFixture(phase: Phase = 'ceo', opts: { methodologyLines?: nu
|
||||
/** Phase-close step 6: the report and the no-op in their own message, then the no-op's result. */
|
||||
const publish = () => {
|
||||
const messageId = nextMessage();
|
||||
report(messageId); use(`noop-${messageId}`, 'Bash', { command: `: autoplan-published ${phase}` }, messageId);
|
||||
report(messageId); use(`noop-${messageId}`, 'Bash', { command: `true autoplan-published ${phase}` }, messageId);
|
||||
result(`noop-${messageId}`, { content: '' });
|
||||
};
|
||||
|
||||
|
||||
@@ -575,7 +575,7 @@ do not launch the downstream skill or open a browser.`,
|
||||
maxSkeletonBytes: 70_000, // Phase-specific outside coverage, native fallback, and harness guard.
|
||||
minUnionBytes: 85_000, // measured union 86,926
|
||||
mustContain: ['6 Decision Principles', 'TASTE DECISION', 'USER CHALLENGE', 'consensus', 'Restore Point'],
|
||||
maxSizeRatio: 1.1215, // Four validated outside invocations replace raw CLI calls; phases keep independent coverage. + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site, and phase-close publishing each report with the `: autoplan-published <phase>` no-op before the next phase entry in a later message (CEO-1); measured 114,320 / 101,979 = 1.1210 (2026-10-07).
|
||||
maxSizeRatio: 1.1215, // Four validated outside invocations replace raw CLI calls; phases keep independent coverage. + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site, and phase-close publishing each report with the `true autoplan-published <phase>` no-op before the next phase entry in a later message (CEO-1); measured 114,320 / 101,979 = 1.1210 (2026-10-07).
|
||||
},
|
||||
spec: {
|
||||
skill: 'spec',
|
||||
|
||||
Reference in new issue
Block a user