agentleFS
phahadekphahadek/claude-orchestrator/skills/gate/SKILL.md

gate

Skill1 starsChanged 3 months agoMITView on GitHub

Run the Manual Verification Gate for a milestone against the gate-state API (readiness → pull runnable items by tier → human disposition → record). Use when the user says "run the gate", "gate check", "verify the gate for milestone X", or starts a Manual Verification Gate pass. Requires the gate-verification service's gate_item / gate_item_event tables and the backend's /api/gate/* routes to be live for the target milestone.

  • Reads credentials

32 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use new, build and 8 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • codebycoreySkill

    new

    Start a new change with a proposal. Use when starting a feature, creating a proposal, or beginning planned work.

    Also in 3 projects that use this

  • Microsoft AzureSkill

    build

    Build the TypeSpec Rust emitter from source. Run this after cloning the repo, after pulling changes, or when TypeScript source files have been modified.

    Also in 5 projects that use this

  • AWSSkill

    ship

    Finalize dev work on an issue branch — update docs, push, and open a PR.

    Also in 8 projects that use this

  • genomewalkerSkill

    checkpoint

    Save a checkpoint of task status, decisions, active files, and next steps

    Also in 3 projects that use this

  • BlockSkill

    plan

    Draft a TDD, PRD, or lightweight plan in the conversation without committing it.

    Also in 4 projects that use this

  • GitHubSkill

    refactor

    Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.

    Also in 3 projects that use this

The file

Show its rules and text

We split this skill into the rules it gives an agent

20 rules. 0 are common, 6 are distinctive, and 14 are very specific to this project.

  • 0

    Common

    Most of the 30 edited versions we read keep these.

  • 6

    Distinctive

    This author's own choices. Worth a look.

  • 14

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every attempt is a gate_item_event — that log is the durable run record.

    line 18
  2. Distinctive:

    Reading a managed project's production data: When a gate item targets a managed project's data (not the orchestrator's own record), observe …

    line 118
  3. Very specific:

    Load the read surface first: Read config/projects/<dir>/context.md (its operator read surface / inspecting-live-state section) and use only …

    line 121
  4. Very specific:

    No env-file DSNs, no write-capable roles: Never source a DSN or credential from a host env file (/etc/<project>/*.env or similar), and …

    line 124
  5. Distinctive:

    Verifier sub-agent briefs: When fanning items out to sub-agents, the brief must carry the project's sanctioned read path verbatim and …

    line 132
  6. Very specific:

    Verify before you claim: Don't assert a state — "this was mis-accreted," "there's no route for X" — until you've checked the source.

    line 154
  7. Very specific:

    A broken baseline is itself a failure: If the bar is unattainable because the system's normal-state value is already broken, record a fail …

    line 233
  8. Very specific:

    Ownership check includes seed items: Before declaring a prod action unowned, check the milestone's seed items (seed-state-client.mjs …

    line 243
  9. Very specific:

    (a) Name the direct, non-pooled DSN: Never the pooled (PgBouncer) one.

    line 256
  10. Distinctive:

    No manufactured load: Never stage a load or isolation test against a shared prod host (e.g.

    line 268
  11. Very specific:

    Items are behaviours, not instances: If an item hard-codes one market/command/payload that is no longer verifiable but the behaviour is, …

    line 276
  12. Very specific:

    If a deploy lands during the run: re-pull readiness once.

    line 313
  13. Distinctive:

    Forbid idle waiting: no background watchers, polling loops, or sleeps beyond ~2 minutes.

    line 324
  14. Very specific:

    Read item.text (and node ~/.claude/scripts/gate-state-client.mjs item <id> for the full record, including prior events, if you need history …

    line 409
  15. Very specific:

    Pre-flight — cross-check Done 🧪 Testing tasks: Before staging any live/E2E check, ask whether a Done 🧪 Testing task already owns this …

    line 413
  16. Distinctive:

    Only when history genuinely can't show the behavior do you move to an active check (step 4).

    line 425
  17. Very specific:

    If no task owns it, file one: create a 🔧 Operational task at Backlog for the mutation (backfills and other prod steps are for an ops task, …

    line 443
  18. Very specific:

    Before reporting, every pulled item with a fail finding (including every fail a verifier returned) must have the three root-cause locks …

    line 617
  19. Distinctive:

    Never rely on an earlier board read: in the 2026-11-05 run 12 needs-setup items named Done owners and 7 fail items had Done follow-ons …

    line 634
  20. Very specific:

    Separate pass from deferred counts — every time: Never report a single "resolved N" number: "resolved" blurs passes and deferrals, and …

    line 655

“Common” is measured: kept by at least 6 in 10 of the 30 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Manual Verification Gate

The gate is orchestrator-tracked state, not a task-body checklist. Every verification item lives as a gate_item row with a classification and a state machine (open → runnable → pass / fail / deferred, with Prod-Mutating passes parked at pending-approval until a human consents). Every attempt is a gate_item_event — that log is the durable run record. There is no dated run-note to write back into a task body, and nothing here is loaded by fetching and parsing a Notion task's body.

This skill is a thin human-driven loop over the gate-state API (packages/backend/src/routes/gateState.ts, business logic in packages/backend/src/gate/gateService.ts), called through the sanctioned node client gate-state-client.mjs (loopback-only, device-authed — never shell out to Notion or hand-fetch a task body for this).

node ~/.claude/scripts/gate-state-client.mjs <command> ...

gate-state-client.mjs is the vendored sanctioned node client (see packages/backend/scripts/gate-state-client.mjs, re-vendored via the /sync-guidelines skill), using $ORCHESTRATOR_DEVICE_TOKEN (host/port default to 127.0.0.1:3000, overridable via $ORCHESTRATOR_BACKEND_HOST / $ORCHESTRATOR_BACKEND_PORT) the same way the other sanctioned session clients (groom-context-client.mjs, staged-intents-client.mjs) do. If ORCHESTRATOR_DEVICE_TOKEN is unset, stop — this skill only runs inside an orchestrator-launched session.


Core doctrine — read this before running a gate

The whole point of a gate session is to take verification work off the operator. Its job is to conserve the operator's time, not spend it — to supply the method, the judgment, and the push itself. The most expensive failure this skill can commit is handing the work back: declaring an item "not closable this session," "needs you to drive," dropping it at the first partial, mass-deferring items you never actually tried to stage, or offering an option-menu ("which do you want?") where the session could have just done the work and reported the result. Every one of those spends operator time that the gate exists to save. Deferral is the disposition-shaped version of the same hand-back — "hard to observe" is a cue to stage the behavior, never to deferred it (Step 3); reserve deferred for the genuinely-rare-and-unsafe-to-manufacture item punted to a later milestone. So the governing rule, above all the mechanics below: exhaust the observe-by-reading path and act; never declare an item un-closable or hand it back — as an escalation or as a disposition — until you actually have. Escalation is legitimate only for a real decision that is the operator's to make, a genuinely blocked resource, or Prod-Mutating consent — never as a substitute for doing the observable work.

Tier decides how you close a hard-to-observe item — "stage it, never defer it" is not tier-blind. The push above is right, but it has been applied too flatly: staging a wedged-group recovery drill was correct; staging a cgroup-OOM test on the live host was not. Read it as:

  • Prod-Mutating → simulate, don't perform. Reproduce the condition the item describes without mutating real state — a scratch object, a forced code path, a synthetic row. If the only way to observe it is to actually harm production, you do not stage it.
  • Trigger hasn't fired → park it with a trace guide, via the not-yet-triggerable disposition. Wait for the natural occurrence instead of forcing one. Parking is not deferral and not a hand-back — provided it carries the trace guide below. This is the legitimate third path between "stage it" and "defer it", and its absence is what made this doctrine read as all-or-nothing. (This was the Opportunistic tier's job; that tier is retired and the disposition now carries it — the item parks at pending on a backoff clock.)
  • A pulled item that needs a UI action or similar only a human can do → reclassify it to Human-Observation (reclassify <id> Human-Observation). That hands it to the operator via the Decision Inbox; do not pass --classification Human-Observation to next.

A pulled item ends in exactly one of five outcomes, and is never left runnable or closed out as noted:

  1. Not-yet-triggerable — waiting on an external trigger that has not happened (not-yet-triggerable; evidence names the trigger and the exact observable that settles it).
  2. Needs-setup — an in-depth setup is required (needs-setup; evidence names the setup).
  3. Human-Observation reclassification — only a human can do the check.
  4. Pass — with the evidence.
  5. Fail — with the evidence, the root cause, and the fix task filed (filedFollowon). A fail without its fix task is rejected (400); a root cause is found in-session, never left for a separate run.
  • Read-Only → just go read it. Unchanged — and it now also covers the bounded, cleanly-reversible scratch write.

The trace guide is what makes a park legitimate — and it is no longer only convention: the server rejects not-yet-triggerable without non-empty evidence naming the observable to watch for. Every non-resolving disposition (needs-setup, a parked not-yet-triggerable item) records the exact observables to capture when the behavior fires: which table or log, which field, which value separates pass from fail, and the query that will settle it. A park with a trace guide is a prepared verification the next session executes in one step; a park without one is a punt wearing better clothes. This pattern emerged organically across a real run and earned its keep — treat it as the standard content of any non-resolving disposition.

The six disciplines below are how that rule is kept:

  • Code ≠ verification. A passing unit test does not close a gate item. A behavior lives in the gate precisely because it is rare / runtime and a code-level test can't exercise it — so verification means observing the behavior actually occurred (in the system's own history, or live), never that the code looks correct. If a unit test could settle it, it was never a gate item. Concretely: "the code is deployed and its unit tests pass" is not a pass — that reasoning has closed items that then had to be reopened. There is no passed-because-I-read-the-code.
  • Verify by reading history first. Most gate items describe a behavior that has already happened in the orchestrator's own operational record — so the first-line strategy is to go read it, not to stage a fresh live scenario. Before any staging, look for a past real occurrence in audit_log, session_events, pull_requests, local_branches, and the git/GitHub record (git -C <repo> log/show …, gh pr/run …). This was the single most productive method across a full gate run; staging is the fallback for behaviors history can't show.
  • Reading a managed project's production data. When a gate item targets a managed project's data (not the orchestrator's own record), observe it only through that project's sanctioned read surface:

    1. Load the read surface first. Read config/projects/<dir>/context.md (its operator read surface / inspecting-live-state section) and use only what it names, in the order it gives.
    2. No env-file DSNs, no write-capable roles. Never source a DSN or credential from a host env file (/etc/<project>/*.env or similar), and never use a write-capable role to read. Gate verification is observation; a read needs no write credential.
    3. No session-level SET (including default_transaction_read_only) on a production connection. If read-only enforcement is needed, it comes from the role, not a session setting — on a transaction-pooled connection a session setting leaks to every other client of that pool.
    4. Verifier sub-agent briefs. When fanning items out to sub-agents, the brief must carry the project's sanctioned read path verbatim and restate rules 2–3. A brief may never hand a sub-agent a raw DSN, an env-file path to source, or a credential.

    Real run (2026-10-11, polimarket-analyser M18): verifier sub-agents briefed with the write-capable pooled $POSTGRES_DSN set a session-level default_transaction_read_only that stuck to shared PgBouncer connections, failing ~12k daemon writes and taking the fleet down until a PgBouncer restart.

  • Exhaust observe-by-reading before declaring "needs a human." "Hand this to you / not this session" is buck-passing when the read-history path hasn't been exhausted. Concrete tells of the failure, all seen in real runs: giving up at the first partial instead of reaching for gh / git / session_events / the code; handing over an option-menu ("which do you want?") instead of doing the work; reaching for a heavy harness (rebuilding a rig to re-run a check) before asking the cheap question — has a Done 🧪 Testing task already verified this? A gate session's job is to observe, and to reach for the harder pass rather than pawn it off; escalate to a human only after reading the operational record genuinely fails to settle the item — or the check is Prod-Mutating (which needs consent by design, a different thing).
  • Verify before you claim. Don't assert a state — "this was mis-accreted," "there's no route for X" — until you've checked the source. Falsify the claim against the code / data first; an over-claim recorded as evidence pollutes the durable event log. And prefer the operator's account of what they did over an inference from timing or logs (procedures.md § Performance / host monitoring, rule 7) — a 26-second gap was read as automation in one run when simply asking would have settled it.
  • "Verified" is a word with a definition — don't narrate past your evidence. The disposition vocabulary is server-enforced; your prose is not, and prose is where this gate's central distinction actually gets blurred. "The code is deployed and the behavior never occurred" is not verification — it is deployment plus absence of contradiction, which is precisely the reasoning Code ≠ verification exists to reject. Real run: an item was narrated as "I verified that item directly" on exactly that basis. The recorded disposition was correctly noted, so the durable record stayed clean — but the narration asserted the one thing this gate exists to police. Say what you observed, in the words of the observation. "Code deployed, no occurrence in the record" is an honest sentence; "verified" is a claim about a behavior you watched happen. If you cannot name the event you observed, you did not verify it.
  • Query defensively — a documented trap you have read is still a trap you will walk into. A wrong query here returns a confident wrong answer, not an error, and this skill's entire output is built on such queries. Five vacuous reads in one run, three of them traps this project's own context.md documents explicitly: a filter on local_branches.task_id (no such column); LIKE '%recover%' matching 50 pushbacks containing the word "recovered"; a truncated task-id prefix reading the wrong task as Done; grepping projected free memory when the emitted string is projected free host memory; and concluding the memory gate emits nothing when the log line lives at the caller. Reading the warnings is not the same as querying defensively. Before you believe a result: confirm the column exists, print one raw row, check your match string against the emitted string, match ids in full, and confirm the zero you got could have been non-zero. Every count that will appear in your report is a claim to validate, not a fact.

⚠️ Two non-obvious staging constraints (both have bitten real runs):

  • A remote-control session cannot mint a per-session stage credential. Items that need a live backend session to stage (the 85f0ad1d-class — anything requiring a real device/session-scoped stage) can't be staged from an RC session. That does not make them unverifiable-so-defer — it means they're verifiable in a live orchestrator-launched session; route them there rather than deferring.
  • A Ready-flip on an auto-launch project auto-dispatches. Marking any 💻 Code task Ready on an auto-launch project immediately launches an unattended worktree — so any staging that flips a task to Ready has a live side-effect. Stage with scratch tasks and clean rollback (archive scratch, restore survivors); never flip a real task to Ready just to observe.
  • Filing a 🔲 Backlog task on an armed milestone is not inert — this is post-file hygiene, not a reason to hesitate before filing. The rule above covers a Ready-flip; this is its quieter sibling. Where auto-launch is armed, a task filed at Backlog can be groomed, implemented, reviewed and merged unattended — one filed task was merged 28 minutes later while the session recorded "left at Backlog, not groomed" as the end state. "I left it at Backlog" describes what you did, not where the task ends up. This never means checking arm/auto-launch state before filing, and never means asking the operator's permission to file because the milestone happens to be armed — a fail always gets its follow-on filed (Step 3, fail disposition). It means: after filing, say so and re-read the task's state before reporting, since it may no longer be sitting inert at Backlog by the time you report.

⚠️ A gate session never carries out 🔧 Operational task scope (this bit the Polimarket M17 gate: reconcile/recompute commands that were the listed steps of a Backlog Operational task were run out of order, and the task's capture requirement became unmeetable):

  • Before staging any prod mutation for a gate item, search the milestone board for a task whose scope owns that mutation. If one exists, the step belongs to that task (/ops); the gate item's disposition is needs-setup naming that task.
  • An operator remark that rejects an option (e.g. "don't leave bad data") is not authorization to run the alternative. Prod-mutating commands need the operator to name the action.
  • If a step owned by another task was run anyway, record exactly what ran (command ids, counts, timestamps, what was not captured) in that task's Implementation notes. The gate event log alone is not enough.

Expect to hand off the final mutating step — that is the stable shape of an RC gate session, not a failure. The harness reliably permits the entire analysis path and blocks the last state-changing action: a systemd write, a wrapped-marker POST, and a group-commit were all blocked in one run while every read and diagnosis ran clean. Plan for it: do the full analysis, then stage the handoff as one paste-able command with its expected output — rather than hitting the block mid-step and improvising. This is not the Prod-Mutating consent gate (a deliberate design feature); it is the harness permission boundary, and it lands on actions this skill otherwise treats as routine. When it blocks, follow procedures.md § The harness permission boundary: try under the generic approval, then explain exactly what was blocked (surface + classifier reason) and ask for explicit approval of that action, and only then ask for an approval-mode change. Never open with "change the permission mode / add a permission rule".

Doctrine rulings and gate scope

  • A broken baseline is itself a failure. If the bar is unattainable because the system's normal-state value is already broken, record a fail and RCA the baseline. Do not restate the bar to fit the broken state. (Polimarket M19: a ≈280 s no-backfill lag plateau was real lag from poison files, not a mis-specified 60 s bar.)
  • Invalid as written, intent observably met → pass on intent. Say so in the first line of the evidence and attach the intent check.
  • An item whose check depends on a 🔧 Operational task's run cannot pass until that run happens. Disposition needs-setup naming the owning task; never restate the item to pass early.
  • Heavy work is still the gate's job. Locate the root cause of every fail in-session and file the fix task with it; "needs a separate /investigate run" is not an outcome.
  • Ownership check includes seed items. Before declaring a prod action unowned, check the milestone's seed items (seed-state-client.mjs readiness / next) as well as the board's 🔧/🔎/🧪 tasks, and read the fix task's own text — it may assign the apply to the deploy. (The pgBackRest host install was once declared unowned without this check.) Known gap: the client has no route listing confirmed or discarded seed items, so those cannot be enumerated; add one when touching the client.

Subagent delegation — DB-safety and no manufactured load

Any brief this session hands to a subagent that reads a project database MUST:

  • (a) Name the direct, non-pooled DSN. Never the pooled (PgBouncer) one. Resolve the concrete DSN names from the project's context.md § Running production commands; do not guess them.
  • (b) Forbid session-level settings. The subagent must never execute a SET (including default_transaction_read_only); any setting is passed as a connect-time option (e.g. the connection's options/-c parameter). State "never execute a SET" in the brief verbatim.
  • (c) Forbid shared helper scripts across verifiers. Each verifier writes and uses its own connection code; none reuses another's helper.

Why: on 2026-10-11 verifier helpers issued a session-level SET default_transaction_read_only=on through the pooled DSN. It leaked via transaction pooling onto shared server connections; every pooled daemon write failed for ~28 minutes and the fleet crash-looped.

No manufactured load. Never stage a load or isolation test against a shared prod host (e.g. forcing an unscoped heavy recompute). Park the item with a trace guide for historical-telemetry verification instead — no manufactured load, ever.

Deferred re-checks are relative to deployment, never a calendar date. A parked/deferred item's trace guide says "re-check N days after the fix deploys (window starts at the relevant daemon's restart onto the fix)", not "re-check at <date>".

Items are behaviours, not instances. If an item hard-codes one market/command/payload that is no longer verifiable but the behaviour is, treat the item as malformed and verify the behaviour more broadly. If an item fails only on a mis-specified literal threshold, restate it with its derivation and put the restatement to the operator rather than recording a fail on the bad number.

Verify the item's goal before proposing a restatement. A restatement proposal must first record a direct observation of the item's goal behaviour (does the backoff, yield, retry or guard actually happen?), not just of the literal the item names. On 2026-11-05 a restatement was proposed for "reconcile passes paused while a fast-lane cycle is in flight" without checking whether the pause happened; done properly, the yield did run at its 10 s cap, but some fast lane held the flag 100% of the time, so the finding was a different one. No observation, no proposal.

A held restatement is recorded, not kept in scratch files. There is no restatement disposition. Record the proposal as a dispositionless (pure-log) event: event <id> '{"evidence":"restatement-proposed: <proposal text>. observed: <the direct observation of the goal behaviour>"}'. State and runnability are unchanged (the item stays pullable), and the log carries the proposal until the operator rules. The evidence MUST contain the goal-behaviour observation.


No run budget — stop on items, not the clock

A gate run has no wall-clock budget. The only stopping conditions are no items left (including the sweeps and completion gate below) or an operator interrupt. Root-cause work is never time-capped.

Never wait for an observation window, natural trigger or reconciler flip inside a run. This rule is about idle waiting, not about working: keep working other items. If an item's window has not elapsed, its natural trigger has not fired, or the gate reconciler has not yet flipped it to runnable, park it immediately as not-yet-triggerable with an interim reading (what the observable shows now) and a trace guide naming the exact timestamp at which the window completes and the query that settles it. "Stage it, never defer it" does not mean sit and wait: a window that has not elapsed cannot be staged, only parked. In-session sleeps, timers and watchers are capped at about 2 minutes in total per wait; never start a longer one. The only exception is a live capture the operator explicitly requested.

Deploy landing mid-run

If a deploy lands during the run: re-pull readiness once; park every window-gated item as not-yet-triggerable using the restart timestamp as the window start (re-check relative to that restart, per the rule above); and verify only checks observable immediately (an EXPLAIN, a single pass, a sentinel). Do not poll for the roll or the reconciler to converge.

Verifier subagent fan-out

Beyond the DB-safety rules above, every verifier brief MUST:

  • Carry no time budget. Verification and RCA agents run until every item in their shard has a disposition; root-cause agents are never time-capped. The brief says so explicitly.
  • Forbid idle waiting: no background watchers, polling loops, or sleeps beyond ~2 minutes. An item whose window is pending is reported as not-yet-triggerable with an interim reading and trace guide, not waited on; the agent moves on to other work instead.
  • Point the agent at the actual budget or spec source. Name the exact page/task that holds it (it may live in a Done investigation's Implementation notes, not a Deferred task). Verifiers once read a Deferred task and concluded "no budget exists" while the budget sat in a Done investigation.
  • Send back an under-trying verifier. An agent that returns any unlocated fail ("root cause not located") or any "not attempted" item is re-dispatched to finish them, whatever time it used.
  • "Deployed" is not "installed" for host config. A merged change under infra/ is not live until it is on the host: verify the /etc/... content and mtime, not just git ancestry. Say so in the brief.
  • Name the project's stored-verdict tables (from its context.md) and the query shape for each. A verifier must query them before concluding "no stored verdict"; that conclusion without having queried those tables is vacuous and is sent back.
  • Require the agent to write its results as a JSON array to a named file in the worktree scratch area (one entry per item: id, disposition, evidence, trace guide), and return only the path and a one-line tally.
  • Never file tasks; return a fix draft keyed by branch. Parallel verifiers filed the same root cause independently (eight duplicate tasks retracted, two already In Progress). A verifier's fail entry carries a fix draft (title, the three root-cause locks) keyed by the code branch at fault, and no filedFollowon. The parent groups fails by branch across all shards and files exactly one task per branch, then records every fail sharing that branch with that one task id.
  • Carry the refusal procedure. A refused command covers that action only: the verifier keeps working every other item and never stops the shard. It records a refused item as needs-setup, with evidence starting refused: <exact command shape> (<classifier reason>), plus the read that would settle it. It never asks the parent to run the refused action for it (permission laundering). The parent re-runs refused items only after the operator changes the approval mode.

The parent records from that file and spot-checks all fails plus a sample of passes against the evidence. It never hand-transcribes verifier evidence into JSON. Before recording a shard whose verifier reports a refusal, the parent checks that it did not mass-convert unattempted items to needs-setup. Record shards as they return, but do not report until every shard is resolved.


Step 0 — Resolve the milestone

Determine the milestone (e.g. "M12") from the user or the active context. Do not improvise one — ask if it's ambiguous.

Step 1 — Check readiness

node ~/.claude/scripts/gate-state-client.mjs readiness --milestone <M>

Returns {status: 'green' | 'blocked', blocking: GateBlockingItem[]}.

Before using the payload for anything — a worklist, any count, or deciding whether work remains (including the Step 4 loop condition) — drop every blocking / parked entry whose classification is the invisible class named in the Core doctrine. Do not read, tally, or mention the dropped entries. If nothing is left after filtering, the session has nothing left to do and reports exactly as it would for an empty worklist (treat it as green).

  • green — every item in the milestone is pass or deferred. Report this to the human and stop; there is nothing left to run.
  • blocked — blocking lists every item not yet resolved, with its classification and current state (after the filtering above). Use this as your worklist map, but pull the actual batch to work through the API in Step 2 — don't disposition straight off this summary list, and don't bulk-load the whole blocking set at once.

Step 2 — Pull one runnable tier at a time

node ~/.claude/scripts/gate-state-client.mjs next --milestone <M> [--classification <C>] [--limit <N>]

Returns up to limit (default 10) runnable items from one tier. Tier pull order, when --classification is omitted: needs-triage → Read-Only → Prod-Mutating — untriaged items surface first, then increasing blast radius. Never request the full runnable set; always let the server pick or explicitly scope one tier.

An empty array means that tier has nothing runnable right now — either move to the next tier explicitly via --classification, or re-run without a classification to let the server advance to the next non-empty tier.

Step 3 — Disposition each item, then record it

For every item in the pulled batch:

  1. Read item.text (and node ~/.claude/scripts/gate-state-client.mjs item <id> for the full record, including prior events, if you need history before re-attempting one).
  2. Pre-flight — cross-check Done 🧪 Testing tasks. Before staging any live/E2E check, ask whether a Done 🧪 Testing task already owns this validation. If one does, the item is a pass (covered-elsewhere — the behavior is done, just done by another task; cite the covering task in evidence, e.g. covered-elsewhere: <taskId>, not a deferred), and the only real question is "what would I do here that the Testing task hasn't?" Don't re-run what's already been observationally verified.
  3. Verify by reading history first (Core doctrine). Go find a past real occurrence in audit_log / session_events / pull_requests / local_branches / git / gh before staging anything. A behavior found in the operational record is a pass — record the pointer to it as evidence. Only when history genuinely can't show the behavior do you move to an active check (step 4). Remember code ≠ verification: reading the source to confirm a code path exists is not the same as observing the behavior happened.
  4. Perform the active check the item describes (only when reading history didn't settle it):
    • Read-Only — mechanical or low-risk checks you can run and judge directly (read code, hit a read endpoint, inspect output).
      • Scratch-write convention: a self-contained scratch write with clean rollback (a throwaway scratch task you create and archive, restoring any survivors) is Read-Only, not Prod-Mutating — its blast radius is bounded and reversible. Reserve Prod-Mutating for a flip of real state (a real task's status, a real config row). This removes the recurring hesitation on staging checks that only ever touch scratch objects.
    • Before staging any prod mutation, check for an owning task (see the Operational-scope constraint in Core doctrine): if a 🔧 Operational task on the milestone board owns it, do not run it — disposition needs-setup naming that task. If no task owns it, file one: create a 🔧 Operational task at Backlog for the mutation (backfills and other prod steps are for an ops task, not for gate runs), disposition the item needs-setup naming that new task, and never ask the operator for consent to run the step yourself.
    • Prod-Mutating — do not self-grant a pass. These are non-mechanical: surface the exact action to the human, get their explicit go-ahead before performing anything that mutates production, and never mark one pass without a human present for it.
    • needs-triage — the item's classification isn't resolved yet, usually a backfill artifact (an entire milestone can land as needs-triage — M11 did). Read item.text and judge which tier it belongs to (mechanical read-only check or bounded scratch write → Read-Only; anything that mutates production → Prod-Mutating). When you're confident, reclassify before dispositioning — don't guess a class or disposition the item blind:

      node ~/.claude/scripts/gate-state-client.mjs reclassify <gateItemId> <classification> [operator]

      classification must be one of Read-Only, Prod-Mutating — the server rejects anything else, including needs-triage itself. Once reclassified, the item is picked up by its new tier on the next next pull (and by the continuous reconciler's auto-run path for Read-Only/Prod-Mutating). Classification affects blast-radius tiering (a Prod-Mutating reclassify changes what needs consent), so treat it as a triage decision: surface your proposed class to the human before applying, and if you're genuinely unsure which tier fits, flag it rather than reclassify blind.

  5. Record the attempt as an event — this is what makes the log durable; do this for every attempt, not just resolving ones:

    node ~/.claude/scripts/gate-state-client.mjs event <gateItemId> \
      '{"disposition":"pass","evidence":"<what you observed>","deploySha":"<verified-sha>"}'

    The disposition vocabulary is a server-enforced closed set (GATE_DISPOSITIONS, gateService.ts — shipped in PR #941): the API rejects anything else with a 400, so an invented value (blocked-unexercised-by-value, partial-…) can no longer become a bespoke non-resolving state. disposition is also optional — an event with no disposition is a pure log entry (records evidence, leaves state unchanged). Put nuance in evidence, never in a made-up disposition:

    dispositionrollup effectwhen
    pass✅ resolvesbehavior observed — in the operational record (history) or by live staging. Evidence: verified: <what you observed>, or covered-elsewhere: <taskId> (a Done 🧪 Testing task / sibling already verified it — done = pass, cite the task; not a deferral)
    fail❌ stays blockingbehavior is broken — present the follow-on Code task, then file it at Backlog unconditionally (filedFollowon: [<taskId>, …] — task ids or seed:<uuid> ids, one per fix task; a placeholder, prose or truncated id is rejected with a 400, so a fail with no fix task yet filed is not recordable as fail; never gated on arm/auto-launch state); the item stays unresolved, re-verified after the fix deploys. "Record fail" is not a resolution.
    needs-setupnon-terminal (stays runnable)an in-depth setup is needed; requires evidence naming the setup (400 otherwise). next skips the item until a later event supersedes it.
    not-yet-triggerablenon-terminal (advances to pending)Read-Only/Prod-Mutating-only, and requires evidence — the triggering condition genuinely hasn't happened yet (no occurrence in history, nothing to stage). Enters a backoff schedule; next skips it until the backoff clock elapses, then it resurfaces runnable for a fresh look.

    Rules the vocabulary encodes:

    • "Hasn't happened yet" on a Read-Only/Prod-Mutating item is not-yet-triggerable, not deferred. deferred means punted to a later milestone; not-yet-triggerable means the same milestone, waiting on its own backoff clock for the trigger to occur naturally. Don't reach for deferred just because nothing has fired yet — that's what not-yet-triggerable is for, and it keeps the item live in-milestone instead of punting it.
    • "Hard to observe" means stage it, not defer it. Staging is the fallback for exactly the behaviors history can't show (Core doctrine). deferred is not a bin for "code-reasoned," "never-occurred-in-prod," or "too fiddly to set up" — those are stageable, and mass-deferring them to clear the board is buck-passing dressed as a disposition. Reach for the harder observation before you ever type deferred.
    • A broken behavior is not fail-and-done — it stays blocking. On every fail: present the follow-on Code task, then file it at 🔲 Backlog and record filedFollowon: <taskId> on the event — unconditionally. This is not gated on whether the milestone's auto-launch is armed: never check arm/auto-launch state before filing, and never ask the operator's permission to file on account of it being armed — that is scope this skill does not have (see the armed-milestone note above, which is about re-reading state after filing, not a pre-file veto). Leave the gate item itself unresolved: it's pending the fix, re-verified after that fix deploys. "Record fail" is not a resolution, and a fail state correctly does not clear the rollup.
    • A follow-up is offered only with its root cause and justification. Before proposing the follow-on (or any other follow-up), have the three root-cause locks from config/task-writing.md § Root cause — the branch, the input that takes it, the fix that follows — plus a measured reason it matters. If the finding is unmeasured or unlocated, investigate it in-session first; never offer it as "file an Investigation." Withdraw a proposal explicitly when the evidence doesn't hold (see Reporting discipline).
    • A mis-accreted / orphaned item → discarded (with evidence), never deferred. Deferred means punted-to-a-later-milestone; a void item that should never have existed is discarded — terminal and non-blocking, but audited. A gate session cannot record discarded through event (rejected with 400); only on the operator's explicit ruling, record it with discard <gateItemId> <reason> [operator] (see Corrective commands). Without that ruling, report the item and leave it open.
    • A compound item that won't isolate → split it into atomic gate_items (task-writing.md § atomic gate items), don't force a bespoke disposition.
    • Two known vocabulary gaps — name them in evidence rather than forcing a fit.
      • Legitimate work that is genuinely unobservable and should not carry forward has no disposition that fits. deferred says punted-to-a-later-milestone (it will carry forward); discarded says void / never-real-work (it wasn't). One run used deferred plus a prose exception — which works exactly once, and thereafter reads as an ordinary punt to everyone downstream. Until the vocabulary gains a term, put the exception in the first line of evidence and flag it in the report; never let the disposition quietly misrepresent the item.
      • needs-setup conflates "tried and couldn't" with "never got a session." Because next skips the item, a transient failure becomes permanent invisibility — a capacity error silently shelved 28 items in one run. Whenever you record needs-setup, state in evidence which of the two it was and what would change it. General rule for any non-resolving disposition that hides an item from the pull: it must distinguish an attempt that failed from an attempt that never happened, or a transient fault becomes a permanent one.

    deploySha is load-bearing on a pass tied to a source commit — record it, don't treat it as optional. It documents the exact SHA the behavior was verified against (evidence hygiene). It also guards a historical bug where a pass recorded without deploySha was auto-reopened when the item's min_deployed_commit filled on a later deploy; that reopen is now fixed in source (a pass is terminal for runnability — gateService.ts:118), but record deploySha regardless — belt-and-suspenders, and correct if your prod predates that fix. Also include evidence (freeform — text, a link, an audit_log / transcript excerpt) and filedFollowon when a fix was filed rather than fixed in place.

    Resolve deploySha at the moment you write the event — never carry one across a session, and never back-correct in bulk. On a live-deploying system production moves during the run (four times in one night), so a SHA resolved at session start is already stale when you use it, and chasing stamps is a treadmill: one run corrected nine and had five more go stale behind it. Re-resolve immediately before each event call. If stamps have already gone stale in bulk, that is not yours to chase — say so and move on. The durable fix is server-side stamping at event-write time rather than trusting a client that may be hours old; that is a finding to file, not a thing to hand-correct.

    Evidence timestamps come from the clock at write time. Run date -u +%Y-%m-%dT%H:%M:%SZ immediately before writing the event and use that value for any time stamped into evidence; never guess, estimate, or reuse a time from earlier in the session. If a time cannot be read from the clock or the source record, omit it.

    For a Read-Only item this resolves the item directly. For a Prod-Mutating item, a pass event parks the item at pending-approval — it does not resolve yet.

  6. Prod-Mutating parks at pending-approval — that is where the session's job ends. Recording the pass event (with its evidence and deploySha) is the session's entire responsibility for a Prod-Mutating item; the item now sits at pending-approval and that is a correct, complete stopping point, not an unfinished step. Resolving it to pass — reviewing the evidence and calling approve — is an operator action taken through the Decision Inbox, not something this skill drives, prompts for, or lists commands to perform. Do not call approve, and do not present pending-approval items to the operator as "approve these" asks in the gate report; move on to the next item.

Step 4 — Loop

Repeat Steps 2–3 for the next tier/batch until readiness reports green, or until the operator interrupts to stop the session (state persists server-side). The session itself does not propose stopping — see the completion contract below.

Completion contract

A gate run dispositions every item it pulls before it reports anything. Running out of items — not running out of appetite or clock — is the only stopping condition available to the session, besides an operator interrupt. There is no wall-clock run budget:

  • Never ask the operator whether to continue. No "want me to keep going?", no continue/stop menu, no "continuing unless you'd rather I stop." The operator interrupts the session if they want to stop; the session never solicits that interrupt.
  • Never emit mid-run progress reports. A status update between batches is a hand-back in disguise — it invites the operator to weigh in on something that isn't theirs to weigh in on yet. Report once, at the end, per the Reporting discipline below.
  • Context budget, batch size, and elapsed effort are the session's own constraints to manage. They are never a reason to check in — pull the next tier/batch and keep going.
  • Genuine operator-only questions are collected, not asked as they arise. Prod-Mutating consent, or any decision that is actually the operator's to make — hold it, keep dispositioning everything else, and raise every held question once, together, alongside the final report.

Completion gate — before any report

Before reporting, every pulled item with a fail finding (including every fail a verifier returned) must have the three root-cause locks from config/task-writing.md § Root cause (the branch, the input that takes it, the fix that follows) and a filed filedFollowon, or be shown to be a pass, a restatement, or a pending window (not-yet-triggerable with a trace guide). "Root cause not located" is not a reportable state: root-causing is the gate session's job, never the operator's. An unlocated fail makes the session re-dispatch root-cause agents (or root-cause it directly) until it is resolved. Root-cause work is never time-capped. A report with a fail that lacks its locks or follow-on, or an item still "not attempted", is a rejected report.

Sweeps — run in the same session, before reporting

Both sweeps finish before the report; neither is left for a separate session.

(a) Needs-setup sweep. next skips needs-setup items, so pull the milestone's needs-setup items explicitly with node ~/.claude/scripts/gate-state-client.mjs owners --project <P> --milestone <M> — one read that lists every needs-setup and fail item with the current status of its named owner (task id parsed from the latest needs-setup evidence) or filed follow-on, and an ownerDone flag. Never rely on an earlier board read: in the 2026-11-05 run 12 needs-setup items named Done owners and 7 fail items had Done follow-ons (ops/investigation tasks the deploy reconciler never reopens) by evening. Every ownerDone: true item is handled now (re-verify or re-disposition; a Done follow-on that never reopens the fail means re-verify it directly). For each item, either perform the named setup and verify it, or re-disposition it to one of the five outcomes with evidence. A needs-setup item may stay only if its setup is owned by a named non-Done task. Refusal-derived items (evidence starting refused:) are re-run only after the operator changes the approval mode; until then they stay with their refusal evidence.

(b) Fail sweep. For each item in fail state whose filed follow-on task has deployed (Done and its commit is live on the host), reopen it and re-verify, recording either a pass with evidence or a fresh fail with RCA (three locks) and a new filedFollowon. Fails whose follow-on has not deployed stay blocking, but each must already carry the three root-cause locks; if one does not, root-cause it now.

Re-pull both sets to confirm they are empty (or contain only the permitted remainders above); never assert it from memory.

Reporting discipline

For a gate, how you report is part of the deliverable:

  • Separate pass from deferred counts — every time. Never report a single "resolved N" number: "resolved" blurs passes and deferrals, and a reader will hear "passed N." Say "passed X, deferred Y" explicitly, so the real verification count is never inflated by punts.
  • Surface the per-pass reason list by default. For a gate, the reason-per-pass (what was observed, where) is the deliverable — give it proactively, don't wait to be asked. A pass with no stated evidence is indistinguishable from a rubber stamp.
  • Never groom or promote the fix tasks a gate surfaces. Filing a follow-on Code task for a broken behavior is correct; bringing it to 🗂️ Ready is not this session's job — a gate session verifies, it does not groom. Leave filed fixes at 🔲 Backlog. (But see the armed-milestone note above — "left at Backlog" is not the same as "inert," so re-read its state before reporting; it is not a reason to have hesitated before filing it.)
  • Offer a follow-up only with its root cause and a measured justification. A proposed follow-up carries the three root-cause locks from config/task-writing.md § Root cause (the branch, the input that takes it, the fix that follows) plus a measured reason it matters. An unmeasured or unlocated finding is investigated in-session before it is reported — never offered as "file an Investigation," and never handed back as a task. When the evidence doesn't hold, withdraw the proposal explicitly.
  • Never claim a sweep is complete without re-pulling it, and never do arithmetic in prose. Two overclaims in one run: a tier sweep (the then-live Opportunistic tier, since retired) was reported finished while ca637934 sat untouched, and a "16 not yet runnable" count was simply wrong. Both were self-corrected — but the operator should not have to audit your arithmetic. Before saying "all X are done", re-run next for that tier and paste what the API returns; before quoting any number, derive it from a command's output rather than from your memory of what you did. A completeness claim is a claim (§ Query defensively).

Corrective commands — reopen & reclassify

Two client commands correct a resolved or mis-tiered item; both are human-driven triage actions, not routine disposition:

node ~/.claude/scripts/gate-state-client.mjs reopen <gateItemId> [reason] [operator]
node ~/.claude/scripts/gate-state-client.mjs reclassify <gateItemId> <classification> [operator]
node ~/.claude/scripts/gate-state-client.mjs discard <gateItemId> <reason> [operator]
  • discard — void a mis-accreted / orphaned item (terminal, non-blocking, one audit row with reason as evidence). Use only on the operator's explicit ruling, passing their reason; dispatched sessions (including gate-verify) are rejected and cannot self-discard.
  • reopen — deliberately move a resolved item (pass / deferred) back to runnable when it needs re-verification: a regression surfaced, the deploy that the pass was recorded against was rolled back, or a pass turned out premature. This is the sanctioned reopen — distinct from the historical auto-reopen bug (a pass is otherwise terminal, gateService.ts:118). Record the reason.
  • reclassify — set the classification of a needs-triage (or mis-tiered) item, as in Step 3. Changes blast-radius tiering, so it's a human triage call.

These commands live in gate-state-client.mjs; if a live run reports "unknown command," the vendored ~/.claude/scripts/ copy has drifted behind the deployed backend — reconcile it by running /sync-guidelines (which re-vendors the client via a local-preserving merge). Never blind-cp the repo copy over the live one — that force-overwrite is exactly what got the old deploy-grooming.mjs removed.


What this skill does not do

  • It does not fetch or parse any task body — gate-load.mjs (Notion-body fetch) and gate-parse.mjs (body parsing) are retired; there is no in-repo equivalent to reach for.
  • It does not write a run-note back to a task — gate_item_event rows are the run record.
  • It does not bulk-load a milestone's full item set — always pull one classification tier at a time via next.
  • It does not call approve on a Prod-Mutating item, or surface pending-approval items as an "approve these" ask — resolving a pending-approval item is an operator action through the Decision Inbox, outside this skill's scope.
  • It does not put verification or EXPLAIN-evidence requirements into PRs, PR descriptions, or prompts/specs for implementing sessions — those carry the code change only. Runtime and plan verification is gate work (config/task-writing.md § Manual Verification Gate); verify at the gate after deploy.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.