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
- amirf147/active-desktop-context-enginetheir own version, first seen Nov 8, 2026
- ronniepinnell/caspertheir own version, first seen Nov 8, 2026
- BSSE23047/nauraiz-devtheir own version, first seen Nov 8, 2026
- per-simmons/claude-game-studiotheir own version, first seen Nov 8, 2026
- blauwtje/exotheir own version, first seen Nov 1, 2026
- Railly/skillstheir own version, first seen Nov 1, 2026
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.
codebycoreySkillnew
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 AzureSkillbuild
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
AWSSkillship
Finalize dev work on an issue branch — update docs, push, and open a PR.
Also in 8 projects that use this
genomewalkerSkillcheckpoint
Save a checkpoint of task status, decisions, active files, and next steps
Also in 3 projects that use this
BlockSkillplan
Draft a TDD, PRD, or lightweight plan in the conversation without committing it.
Also in 4 projects that use this
GitHubSkillrefactor
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
Other versions32 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 32
- plugin87/ux-ui-agent-skills★ 3.1ksame name
- guardana/guardana★ 203same name
- skillberry-ai/cap-evolve★ 69same name
- assaio/assaio★ 63same name
- Cleveland-RD/hivehouse★ 46same name
- ProjectAJ14/eklavya★ 29same name
- Railly/skills★ 11same name
- BenjaminG/ai-skills★ 4same name
- dadwadw233/team-collab-skills★ 4same name
- per-simmons/claude-game-studio★ 4same name
22 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository10 other files it gives its agents.
- deploySkill
- designSkill
- docsSkill
- groomSkill
- investigateSkill
- milestone-seedSkill
- milestone-wrapSkill
- opsSkill
- sync-guidelinesSkill
- wrapSkill
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
- Very specific: line 18
Every attempt is a gate_item_event — that log is the durable run record.
- Distinctive: line 118
Reading a managed project's production data: When a gate item targets a managed project's data (not the orchestrator's own record), observe …
- Very specific: line 121
Load the read surface first: Read config/projects/<dir>/context.md (its operator read surface / inspecting-live-state section) and use only …
- Very specific: line 124
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 …
- Distinctive: line 132
Verifier sub-agent briefs: When fanning items out to sub-agents, the brief must carry the project's sanctioned read path verbatim and …
- Very specific: line 154
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.
- Very specific: line 233
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 …
- Very specific: line 243
Ownership check includes seed items: Before declaring a prod action unowned, check the milestone's seed items (seed-state-client.mjs …
- Very specific: line 256
(a) Name the direct, non-pooled DSN: Never the pooled (PgBouncer) one.
- Distinctive: line 268
No manufactured load: Never stage a load or isolation test against a shared prod host (e.g.
- Very specific: line 276
Items are behaviours, not instances: If an item hard-codes one market/command/payload that is no longer verifiable but the behaviour is, …
- Very specific: line 313
If a deploy lands during the run: re-pull readiness once.
- Distinctive: line 324
Forbid idle waiting: no background watchers, polling loops, or sleeps beyond ~2 minutes.
- Very specific: line 409
Read item.text (and node ~/.claude/scripts/gate-state-client.mjs item <id> for the full record, including prior events, if you need history …
- Very specific: line 413
Pre-flight — cross-check Done 🧪 Testing tasks: Before staging any live/E2E check, ask whether a Done 🧪 Testing task already owns this …
- Distinctive: line 425
Only when history genuinely can't show the behavior do you move to an active check (step 4).
- Very specific: line 443
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, …
- Very specific: line 617
Before reporting, every pulled item with a fail finding (including every fail a verifier returned) must have the three root-cause locks …
- Distinctive: line 634
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 …
- Very specific: line 655
Separate
passfromdeferredcounts — every time: Never report a single "resolved N" number: "resolved" blurs passes and deferrals, and …
“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.
Files
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-triggerabledisposition. 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 theOpportunistictier's job; that tier is retired and the disposition now carries it — the item parks atpendingon 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-Observationtonext.A pulled item ends in exactly one of five outcomes, and is never left runnable or closed out as
noted:
- 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).- Needs-setup — an in-depth setup is required (
needs-setup; evidence names the setup).- Human-Observation reclassification — only a human can do the check.
- Pass — with the evidence.
- 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-triggerablewithout non-empty evidence naming the observable to watch for. Every non-resolving disposition (needs-setup, a parkednot-yet-triggerableitem) 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:
- 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. - No env-file DSNs, no write-capable roles. Never source a DSN or credential
from a host env file (
/etc/<project>/*.envor similar), and never use a write-capable role to read. Gate verification is observation; a read needs no write credential. - No session-level
SET(includingdefault_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. - 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_DSNset a session-leveldefault_transaction_read_onlythat stuck to shared PgBouncer connections, failing ~12k daemon writes and taking the fleet down until a PgBouncer restart.- Load the read surface first. Read
- 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
partialinstead of reaching forgh/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 isProd-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
evidencepollutes 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.mddocuments explicitly: a filter onlocal_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; greppingprojected free memorywhen the emitted string isprojected 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
failalways gets its follow-on filed (Step 3,faildisposition). 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 isneeds-setupnaming 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 agroup-commitwere 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 theProd-Mutatingconsent 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, followprocedures.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
failand 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-setupnaming 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.mjsreadiness/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'soptions/-cparameter). 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 startingrefused: <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 ispassordeferred. Report this to the human and stop; there is nothing left to run.blocked—blockinglists 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:
- Read
item.text(andnode ~/.claude/scripts/gate-state-client.mjs item <id>for the full record, including priorevents, if you need history before re-attempting one). - 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 adeferred), 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. - Verify by reading history first (Core doctrine). Go find a past real
occurrence in
audit_log/session_events/pull_requests/local_branches/git/ghbefore 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. - 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, notProd-Mutating— its blast radius is bounded and reversible. ReserveProd-Mutatingfor 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.
- Scratch-write convention: a self-contained scratch write with clean
rollback (a throwaway scratch task you create and archive, restoring any
survivors) is
- 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-setupnaming 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 itemneeds-setupnaming 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
passwithout 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). Readitem.textand 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]classificationmust be one ofRead-Only,Prod-Mutating— the server rejects anything else, includingneeds-triageitself. Once reclassified, the item is picked up by its new tier on the nextnextpull (and by the continuous reconciler's auto-run path forRead-Only/Prod-Mutating). Classification affects blast-radius tiering (aProd-Mutatingreclassify 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.
- Read-Only — mechanical or low-risk checks you can
run and judge directly (read code, hit a read endpoint, inspect
output).
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.dispositionis also optional — an event with no disposition is a pure log entry (recordsevidence, leaves state unchanged). Put nuance inevidence, never in a made-up disposition:disposition rollup effect when pass✅ resolves behavior observed — in the operational record (history) or by live staging. Evidence: verified: <what you observed>, orcovered-elsewhere: <taskId>(a Done 🧪 Testing task / sibling already verified it — done = pass, cite the task; not a deferral)fail❌ stays blocking behavior is broken — present the follow-on Code task, then file it at Backlog unconditionally ( filedFollowon: [<taskId>, …]— task ids orseed:<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. "Recordfail" is not a resolution.needs-setupnon-terminal (stays runnable)an in-depth setup is needed; requires evidencenaming the setup (400 otherwise).nextskips the item until a later event supersedes it.not-yet-triggerablenon-terminal (advances to pending)Read-Only/Prod-Mutating-only, and requiresevidence— the triggering condition genuinely hasn't happened yet (no occurrence in history, nothing to stage). Enters a backoff schedule;nextskips it until the backoff clock elapses, then it resurfacesrunnablefor a fresh look.Rules the vocabulary encodes:
- "Hasn't happened yet" on a
Read-Only/Prod-Mutatingitem isnot-yet-triggerable, notdeferred.deferredmeans punted to a later milestone;not-yet-triggerablemeans the same milestone, waiting on its own backoff clock for the trigger to occur naturally. Don't reach fordeferredjust because nothing has fired yet — that's whatnot-yet-triggerableis 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).
deferredis 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 typedeferred. - A broken behavior is not
fail-and-done — it stays blocking. On everyfail: present the follow-on Code task, then file it at 🔲 Backlog and recordfiledFollowon: <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. "Recordfail" is not a resolution, and afailstate 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), neverdeferred. Deferred means punted-to-a-later-milestone; a void item that should never have existed isdiscarded— terminal and non-blocking, but audited. A gate session cannot recorddiscardedthroughevent(rejected with 400); only on the operator's explicit ruling, record it withdiscard <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
evidencerather than forcing a fit.- Legitimate work that is genuinely unobservable and should not carry forward has no
disposition that fits.
deferredsays punted-to-a-later-milestone (it will carry forward);discardedsays void / never-real-work (it wasn't). One run useddeferredplus 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 ofevidenceand flag it in the report; never let the disposition quietly misrepresent the item. needs-setupconflates "tried and couldn't" with "never got a session." Becausenextskips the item, a transient failure becomes permanent invisibility — a capacity error silently shelved 28 items in one run. Whenever you recordneeds-setup, state inevidencewhich 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.
- Legitimate work that is genuinely unobservable and should not carry forward has no
disposition that fits.
deployShais 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 withoutdeployShawas auto-reopened when the item'smin_deployed_commitfilled on a later deploy; that reopen is now fixed in source (a pass is terminal for runnability —gateService.ts:118), but recorddeploySharegardless — belt-and-suspenders, and correct if your prod predates that fix. Also includeevidence(freeform — text, a link, anaudit_log/ transcript excerpt) andfiledFollowonwhen a fix was filed rather than fixed in place.Resolve
deployShaat 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 eacheventcall. 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:%SZimmediately before writing the event and use that value for any time stamped intoevidence; 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-Onlyitem this resolves the item directly. For aProd-Mutatingitem, apassevent parks the item atpending-approval— it does not resolve yet.- "Hasn't happened yet" on a
- Prod-Mutating parks at
pending-approval— that is where the session's job ends. Recording thepassevent (with its evidence anddeploySha) is the session's entire responsibility for aProd-Mutatingitem; the item now sits atpending-approvaland that is a correct, complete stopping point, not an unfinished step. Resolving it topass— reviewing the evidence and callingapprove— is an operator action taken through the Decision Inbox, not something this skill drives, prompts for, or lists commands to perform. Do not callapprove, 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-Mutatingconsent, 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
passfromdeferredcounts — 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
Opportunistictier, since retired) was reported finished whileca637934sat 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-runnextfor 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 withreasonas 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 thereason.reclassify— set the classification of aneeds-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-cpthe repo copy over the live one — that force-overwrite is exactly what got the olddeploy-grooming.mjsremoved.
What this skill does not do
- It does not fetch or parse any task body —
gate-load.mjs(Notion-body fetch) andgate-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_eventrows 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
approveon aProd-Mutatingitem, or surface pending-approval items as an "approve these" ask — resolving apending-approvalitem 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.
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
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.

