Skip to content

fix: /autoplan gate makes each phase publish in order after its report (GST-1814) - #4135

Merged
capy-ai[bot] merged 5 commits into
mainfrom
capy/gst-1814-autoplan-gate-order
Nov 7, 2026
Merged

capy-ai[bot] merged 5 commits into
mainfrom
capy/gst-1814-autoplan-gate-order

Conversation

@garrytan

@garrytan garrytan commented Nov 7, 2026 •

Copy link
Copy Markdown
Owner
{
  "schema_version": 1,
  "pr": 4135,
  "head": "15c30dcbec67079c1e3b390b6a3e4b9bce74ae80",
  "version": "1.98.0.0",
  "base": "main",
  "gate": {
    "targeted": {
      "pass": 98,
      "fail": 0,
      "skip": 0
    }
  },
  "session_kind": "unattended",
  "head_tree": "d532a97010a87e199977c81bd327ed036a713325",
  "product_patch_id": "402f55fd6e350c276b0e0c9693b5fe3b12cba6a4",
  "gstack_version": "1.98.0.0",
  "review_depth": {
    "depth": "full",
    "class": "code",
    "runtime_lines": 240,
    "rules": "c6a079fdf812f479",
    "base": "e58be617f18606e7e486a875f502dfc50807e626",
    "registry": "e58be617f18606e7e486a875f502dfc50807e626",
    "gate_scan": {
      "tagged": 0,
      "head": "15c30dcbec67079c1e3b390b6a3e4b9bce74ae80"
    },
    "reasons": [
      "risk:security",
      "runtime=240"
    ],
    "risk": [
      "security"
    ],
    "mode": "panel"
  },
  "pregate": "requires-remote cleared @d532a97010a8 (8 checks, current for 15c30dcbec67)",
  "requires_remote": [
    "free-tests.yml/cso-macos-launcher: https://github.com/garrytan/gstack/actions/runs/42401475097/attempts/1",
    "free-tests.yml/macos-named-regressions: https://github.com/garrytan/gstack/actions/runs/42401475097/attempts/1",
    "windows-setup-e2e.yml/windows-setup: https://github.com/garrytan/gstack/actions/runs/42400801100/attempts/1",
    "windows-smoke.yml/windows-smoke: https://github.com/garrytan/gstack/actions/runs/42400801292/attempts/1",
    "pregate.json/remote_gate: remote-gate tree=d532a97010a8 tests=36119 skipped=105 runs=42401475097/1 profiles=ci-reuse/free-tests heads=15c30dcbec67 verified=2026-11-07T18:35:37.208Z"
  ],
  "issues": {
    "fixes": [],
    "refs": [],
    "flakes": []
  },
  "docs": {
    "status": "current",
    "scope": "docs/ and README.md (docs/skill-gates.md, autoplan phase-close template, routing doc)"
  },
  "review_loop": {
    "passes": 1,
    "blocking_found": 1,
    "deferred": 0,
    "noted": 3,
    "fixed": 3,
    "scope": "frozen",
    "fix_checks": 2,
    "rounds": 3,
    "ledgers_read": 5,
    "dispatches": {
      "specialist": 3,
      "red-team": 1
    }
  }
}

What changed

The E2 replay tests at main (10 of 14 fail) and on this branch (14 of 14 pass): each phase now publishes in order, after its own report

The E2 replay tests at main (10 of 14 fail) and on this branch (14 of 14 pass): each phase now publishes in order, after its own report.

Before/after as text: The E2 replay tests at main (10 of 14 fail) and on this branch (14 of 14 pass): each phase now publishes in order, after its own report
--- Before · before.txt · captured output
+++ After · after.txt · captured output
+(pass) sequence 1: stops before any work never exhaust the budget, each names the CEO report as due, and report-less publishes are refused
-(fail) sequence 1: stops before any work never exhaust the budget, each names the CEO report as due, and report-less publishes are refused
 (pass) sequence 1: once a report exists, three blocks at the same step exhaust it
+(pass) an exhausted run no longer refuses publishes
+(pass) sequence 1: the 12-block ceiling ends a run that never reports
+(pass) sequence 1: progress resets the per-step budget, so a run that advances each block is never exhausted mid-pipeline
+(pass) sequence 2: Eng's report first is refused at publish, and back-filling CEO and DX does not make the early Eng count
+(pass) before scope is known, CEO publishes first and later phases follow in forward order
+(pass) a report written after the publish call in the same message does not count for it
+(pass) sequence 3: publishes with no report are refused one by one, naming the report each one needs
-(fail) an exhausted run no longer refuses publishes
-(fail) sequence 1: the 12-block ceiling ends a run that never reports
-(fail) sequence 1: progress resets the per-step budget, so a run that advances each block is never exhausted mid-pipeline
-(fail) sequence 2: Eng's report first is refused at publish, and back-filling CEO and DX does not make the early Eng count
-(fail) before scope is known, CEO publishes first and later phases follow in forward order
-(fail) a report written after the publish call in the same message does not count for it
-(fail) sequence 3: publishes with no report are refused one by one, naming the report each one needs
 (pass) negative control: a run that reports and publishes every phase in order is never refused and releases with zero blocks
+(pass) negative control: republishing an already published phase is allowed and changes nothing
-(fail) negative control: republishing an already published phase is allowed and changes nothing
 (pass) the publish check never refuses with the switch off, outside /autoplan, for other commands, in runner mode or in a subagent
+(pass) the PreToolUse shim hands only a publish no-op to the hook, and fails open without bun
-(fail) the PreToolUse shim hands only a publish no-op to the hook, and fails open without bun
 (pass) a publish whose own tool call never reaches the transcript is let through (fail open)
+ 14 pass
+ 0 fail
- 4 pass
- 10 fail
 

The opt-in /autoplan gate (--autoplan-gate, GST-1666) now checks each phase's publish no-op before it runs, not only the final stop.

  • Publish check (new PreToolUse hook on Bash, same switch). true autoplan-published <phase> is refused unless that phase is the next applicable one and its Phase N complete report was sent since the previous publish, in or before the publish message. The refusal's first body line is Next due: the Phase N (<name>) report. Before scope is known, CEO must publish first. A released or exhausted run no longer refuses publishes.
  • In-order counting at Stop. A publish with no report, or for a phase that is not due yet, never counts, and a later report doesn't rescue it.
  • Budget. Stop blocks count against the step they name (3 per step, reset when the step advances). Blocks before the run's first phase report count against no step. 12 blocks end any run.

Why

GEVA-2's E2 eval (3 told Opus 5.5 sessions, 1 exposed) failed only in the report step: one session spent all 3 Stop blocks on missing=ceo before doing any work, one published Eng's report first and back-filled CEO and DX, and one published everything with no reports. With the gate on, each phase now has to publish in order, right after its own report, and blocks before any work no longer use up the run's budget.

Blast radius

Off by default and unchanged with the switch off; with it on, setup registers one more hook (PreToolUse, matcher Bash) whose shim starts bun only for the publish command. The publish check fails open when there is no /autoplan load, in runner mode, for a subagent's call, or when the call never shows in the transcript within 2 s, and the Stop check still applies then (walked in test/autoplan-stop-gate.test.ts; the real-CLI smoke passed 24/24 at b0fa3f6).

Approvals

Approved: yes (record r-d2a4916e8f04). The full approval record stays private: this repository is public or its visibility is unknown.
Plan: exempt: bug fix
Plan check: exempt bug fix

Summary

/autoplan sessions under the gate kept publishing phases with no report or out of order, and spent their Stop budget before doing any work. The gate now refuses those publishes before they run, counts only in-order publishes at Stop, and budgets Stop blocks per step. Review fixes on top: CEO must come first before scope is known, an exhausted run stops refusing publishes, and the publish check reads the transcript only up to the call it decides.

Test Coverage

All new code paths have test coverage (each of the 8 changed runtime files has a changed test naming it).
Tests ran: targeted 98

Pre-Landing Review

Pre-Landing Review: 9 issues — 3 auto-fixed, 0 asked, 0 deferred, 6 noted.

  • [FIXED] lib/autoplan-stop-gate.ts: before scope was known, Eng could publish before CEO → CEO always comes first (red-team).
  • [FIXED] hosts/claude/hooks/autoplan-stop-gate.ts: an exhausted run kept refusing publishes → a released or exhausted run no longer refuses (red-team).
  • [FIXED] lib/skill-gates.ts: the publish check read the whole transcript, so a report written after the publish call satisfied it → the transcript is cut after the call being decided (outside voice, granted fix round a-1284e408c656).

REVIEW_LOOP: passes=1 blocking_found=1 deferred=0 noted=3 fixed=3 scope=frozen ledgers_read=5 fix_checks=2 dispatches=4

Gate edits: none detected (9 listed, 6 read; patterns: RH-1,RH-3,RH-4,RH-12,RH-13,RH-14,RH-15,RH-16; unlisted idioms: none)

Outside review: completed (openai/gpt-6.1-sol host subagent), 1 finding, fixed in 923dfb3. Native adversarial: completed, no blocking findings.

Review notes

Below the blocking floor; shipped as notes, not fixed:

  • lib/autoplan-stop-gate.ts dueNext: before scope is known, a publish behind an already-published later phase names that published phase as "Next due" (message only; Stop holds the run at missing=scope).
  • lib/autoplan-stop-gate.ts checkRun: a publish of a phase scope rules out is allowed at PreToolUse but listed as out of order at Stop and resets the report window.
  • hosts/claude/hooks/autoplan-stop-gate.ts: the unsettled fail-open is traced in the hook log but not counted in the session record, and gstack-skill-gates report shows neither it nor denied.
  • MAX_RUN_BLOCKS is 12 (was 3 per run): intentional and documented, but up to 12 forced continuations when the gate misreads a run.
  • The publish check depends on Claude Code's PreToolUse tool_use_id and agent_id fields; verified by the real-CLI smoke at b0fa3f6, not re-run at this head.
  • lib/skill-gates.ts: cutting at the decided call also drops earlier calls' results from the same message, so a message with two publishes refuses the second one (the skill already requires one publish per message).

Exploratory QA

Not run: no exploratory QA probes were required for this change. Proof is the hook-level E2 replays above.

Design Review

No frontend files changed — design review skipped.

Eval Results

No prompt-related files changed — evals skipped. The selector fell back to its full profile because setup changed and listed the behaviors as scheduled/release coverage, not PR passes.

Review bot comments

No review bot comments.

Plan Completion

Plan completion audit: not run (no plan is bound to this branch and no docs/designs/ file matches). Fix: add "Plan: " to the PR body, or run /autoplan.

Issues

Refs p1/red-team#1
Refs p1/red-team#3
Refs p1v/outside-voice#1

TODOS

No TODO items completed in this PR.

Documentation

Status: current. The branch already updated its documentation and I found no factual drift.

Scope: base e58be61..15c30dc, 13 committed files (the /autoplan gate's publish-order check, GST-1814); no staged, unstaged or new files. I discovered docs that mention the autoplan gate and its publish no-op, including authored templates.

Documentation health:

  • docs/skill-gates.md: Current (already updated on this branch; claims match the code)
  • autoplan/sections/phase-close.md.tmpl: Current (report and publish in one message, which the check accepts)
  • docs/autoplan-guard-troubleshooting.md: Current (covers a different guard; no conflict)
  • docs/skill-routing-and-contracts.md: Current (links to skill-gates.md)

Coverage debt: the autoplan publish check has reference and explanation coverage in docs/skill-gates.md but no how-to or tutorial (a common gap). Diagram drift: none.

Test plan

  • bun test test/autoplan-stop-gate.test.ts test/skill-gates.test.ts test/setup-skill-hooks-gate.test.ts test/bun-entry-isolation.test.ts test/module-size-ratchet.test.ts: pass, tests_ran=98
  • CI free suite at the pushed head: carried by gstack-ci-carry (see the receipt's pregate line)

🤖 Generated with Claude Code

Open in Capy

garrytan and others added 2 commits November 7, 2026 17:41
…GST-1814)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…s report; per-step Stop budget that never exhausts before the first report (GST-1814)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@trunk-io

trunk-io Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

😎 This pull request was merged.

garrytan and others added 3 commits November 7, 2026 18:00
Before scope is known, CEO publishes first; an exhausted or released run no longer refuses publishes.

Review-Finding: p1/red-team#1 reader=red-team
Review-Finding: p1/red-team#3 reader=red-team
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…cides

Review-Finding: p1v/outside-voice#1 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…nger refuses publishes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@capy-ai
capy-ai Bot marked this pull request as ready for review November 7, 2026 18:41
@capy-ai
capy-ai Bot merged commit ddbf1c9 into main Nov 7, 2026
69 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant