This page covers the mechanics. For why the loop works this way, see The Idea Behind the Loop.
Start with one item:
/devstride:build-item
build-item selects the next unblocked leaf, marks it In Progress, creates a branch, builds and reviews it, merges it through the configured path, updates the DevStride item, and stops after that iteration. Once you trust one pass, /loop /devstride:build-item repeats the process across the plan.
Every iteration begins by resolving the plan’s delivery profile and announcing it with its source. The first match wins:
prototype, standard, extended, or enterprise — anywhere in the invoking skill’s arguments; build-item accepts one, as plan does;Delivery profile: marker in the plan root’s description — a one-off has no plan root, so no marker;profile in .claude/ds-config.json;standard.The profile sets how many readers the build engine fans out to understand the item, the widest adversarial review breadth it may choose, which verified findings are fixed in the item and which are deferred with a rationale, how many rounds the local CLI reviewer gets at a merge boundary, the local gate an item must pass before it merges, and how long the loop waits for a cloud reviewer. The normal target of two adversarial cycles is the same under all four. Under extended only the size-shaped settings move — more understand readers (up to four) and a review breadth that may reach high-risk for a large diff — while the fix floor, the local gate, verification and review cycles are exactly standard’s. See Choose a delivery profile for the four profiles side by side.
Explicit configuration wins over the profile. epicIntegrationBranches.autoRelease, epicIntegrationBranches.fastStoryMerges.enabled and review.pollTimeoutMinutes stand whenever they are present in the file; the profile supplies a value only when the key is absent. A contradiction is honored and reported, never silently resolved.
The three review.* CI-ordering flags work differently: they describe what your workflows support — a draft hold, and a ready flip that releases CI — rather than a per-run choice. Review first and CI last is a floor under every profile, prototype included: where the workflows support the hold, every profile uses it. A repository that cannot prove the hold is reported as a setup blocker rather than quietly running CI alongside review.
review.localCommand is different: it names the local CLI reviewer, it does not schedule it. When present, that engine reviews every release pull request and every pull-request-path item under every profile, and never a fast-merged item that has no pull request — under any profile, prototype included. The profile decides only how many rounds it gets at those boundaries: one under prototype, standard and extended, two under enterprise. null removes it everywhere.
review.localAssistCommand is support rather than a review round. When configured, the build engine may consult it once, read-only, for an ambiguous cross-module design, a critical contract, or a stubborn diagnosis. It does not write, does not satisfy the merge review, and does not run on routine work.
| Situation | Working base | Item pull request? | Later release |
|---|---|---|---|
| Planned leaf under a release unit; release-unit branches and fast merges enabled | The release unit’s integration branch | No. Local review, local checks, local merge. | One full-diff release pull request carries the completed unit to epicIntegrationBranches.releaseTarget. |
| Planned leaf under a release unit; fast merges disabled | The release unit’s integration branch | Yes, targeting that integration branch. | The completed unit still gets its release pull request. |
| Release-unit branches disabled, or no release-unit ancestor | baseBranch | Yes, targeting baseBranch. | No separate release-unit branch path for that item. |
One-off, support train configured (supportTrain.branch, and release.mergeTrainBeforeCut: true) | The support train | No when supportTrain.fastMerges is true: local review, local checks, local merge onto the train. Otherwise yes, targeting the train. | The train's pull request into baseBranch, which release opens and fully reviews before every cut. The item is Done only when the train reaches baseBranch. |
One-off with no support train, or one whose change touches release.releaseBranchFixExclusions (deploy configuration, migrations) | baseBranch | Yes, targeting baseBranch. | None. The item pull request is its full release gate. |
A one-off may be filed under a release unit, so the skill does not infer its path from ancestry. It detects the one-off from the leaf type, title numbering, and dependency relationships, then explicitly bypasses release-unit branch derivation: a one-off never lands on an epic's branch.
Why a train: every merge into the base branch pays for its own CI run and, in many repositories, a deploy. One-offs that batch on the train get one full review and one CI pass at release time — the same bargain as an epic's release pull request — and the train ships with every release, so nothing waits longer than the next cut. Deploy configuration and migrations never ride it, because the train reaches production soon after it merges; they deserve their own pull request, their own CI and an earlier deploy to a development stage. The exclusion is checked against the spec when the item starts and again on the real diff just before anything merges onto the train, since a review fix can add such a file.
Empty arguments mean “next.” You can also name a leaf, say next under I1234, or pass a parent item number by itself to scope selection under that plan.
The next item is the highest-priority open leaf whose blockers are Done, inside the earliest-dated open parent item. A blocker in the same release unit also counts once it has landed, meaning the unit's integration branch shows its merge in its git history (the merged status alone never counts, since a column of that name often holds an open code review); a blocker in another release unit counts only when Done, so the next unit waits until the previous one ships. Naming a leaf by number skips this selection, never the check that it is not already Done or landed: such a leaf is reported where it stands, not built again. Relationships are fetched explicitly because normal summary projections omit them.
The working-base precedence is:
integrationBranch — refused when it names the production branch, the release source, a protected branch or a release branch;epicIntegrationBranches.enabled is true;supportTrain.branch is set and releases merge it (release.mergeTrainBeforeCut), unless its change touches the fix-exclusion paths;baseBranch.The release-unit ancestor is found by walking the entire parent chain and fetching each ancestor’s work type. The integration branch is created from a fresh base branch on the unit’s first item and then reused; its date is the creation date, not the current date. When no branch matches the configured pattern but one on origin carries the epic’s number under an older naming, the loop stops and asks rather than cutting a second integration branch for the same epic.
The precedence is code, not prose. From 3.9.0 one dependency-free helper, skills/build-item/scripts/routing.py, decides where an item lands and names its branches — JSON in, JSON out — and build-item follows its answer. Codex, a script or a person can run the same file and get the same answer. routing-fixtures.json beside it holds the canonical examples, so a repository that implements the rule in its own tooling can test that tooling against them and fail when the two drift; the DevStride repository’s ds work command is held to them that way. Why: the rule used to live only in skill prose, and every agent that re-derived it was a chance to land work on the wrong branch.
origin first, read what other authors landed on the working base since the last handoff, and say so when another session appears active. Then show the ready set, check for human or infrastructure gates, and compare the item’s full spec with the current code.branch-feature stops on a dirty tree and branches from the resolved working base.ultracode-build understands the contract, implements in small green commits, and runs a mandatory maximum-effort Claude review whose breadth scales with risk, up to the profile’s ceiling. Security is always forced on a diff touching the authentication boundary, and from 3.4.0 any lens the repository declares in review.mandatoryLenses is forced the same way whenever the diff touches its paths — one focused finder each, in the story risk check and again at the merge-boundary review, which no profile or breadth ceiling removes. See Mandatory review lenses.pr and review.epicIntegrationBranches.mergedStatusName, Review by default) with a comment naming the merge, and becomes Done when the unit's release pull request merges. A one-off merged onto the support train gets a comment, keeps its status, and becomes Done when the release carries the train into the base branch. Then set the branch-to-merge date window, link the pull request or record the fast-merge commit, and reconcile material differences between the planned and shipped spec.epicIntegrationBranches.autoRelease is true. When the release pull request merges, the release unit is closed out first (a comment naming the pull request, and Done where it tracks a status), then every landed leaf is marked Done with a comment linking it; their dates stay those of their merge onto the unit's branch. When nobody closes it out (an owner merged the release by hand, or a run stopped right after the merge), the next /devstride:build-item <root> does it before choosing what to build. Setup writes it false for the standard, extended and enterprise profiles, so a newly configured repository stops at release-ready for an owner; for prototype it writes true and says so.The skill renders its own numbered progress table as it moves. The skill file states a goal, the rules that must hold and its hard floors, and names its steps 0–8 with a 6.5 so the progress table and other skills can refer to them; the list above is a reader-oriented summary, not those internal labels.
From 1.2.0, a review verdict that rests on having looked — a browser, a running service, a manual run — reports "verified X via path Y", never a bare "verified". Behavioural states are usually reachable by more than one route, and a fix confirmed on one of them proves nothing about the others, so the verdict lists the routes to the same state and says which were and were not exercised. A reviewer later finding an untried path is the process working. The engine also asserts what it measures — scoping checks to the live container rather than a stale one — and rules out an expired session or a service that is not running before reading code.
From 3.3.0, the first act of build-item and of release is git fetch --prune origin, followed by a read of what other people — and other sessions of yours — landed on the branches the run will touch. A branch push by another author, an open pull request that is not yours, or a merge you did not make is evidence that another session is active, and the loop says so explicitly before touching that branch. Every fact carried in from a memory handoff — the integration branch, the last item that shipped, "nothing else has landed" — is treated as a claim to verify against origin or gh before it is acted on; when the two disagree, the remote wins. A checkout can be current while the conversation is not, which is why this is a step at the start of every iteration rather than something the branch step happens to do later. A repository can also opt in to a bounded fetch at every session start with localEnvironment.fetchOnSessionStart; it shortens the window but does not replace the step. Rationale: skills/build-item/references/ground-truth-at-start.md in the plugin repository.
Two rules apply to everything the loop says to you. A claim about repository or delivery state — merged, published, CI green, "nothing else has landed", another session active — is read from origin, gh, or the live target at the moment it is made, never from the local checkout, a summary, or memory; a claim that cannot be backed that way is labelled unverified wherever it is repeated. And every handoff, plan, or reviewer prompt ends with two explicit lists, steps the loop does and steps you must do yourself — a login, an approval, a dashboard check, a command only you can run — with an empty second list saying nothing required from you.
With epicIntegrationBranches.fastStoryMerges.enabled: true, a planned leaf on an integration branch receives:
prototype to type-checks, the full test suite, and lint under enterprise;--no-ff merge onto the release-unit branch after all findings and checks settle.There is no item pull request, cloud reviewer, CI run, or review-thread bookkeeping. Those gates move to the completed release unit; they are not declared passed on the item.
Fast mode requires at least one local review engine. If no build-time Claude pass covered the diff and no local CLI is available, the item falls back to the pull-request path. Under prototype, fast mode applies whenever fastStoryMerges.enabled is absent or true; a present false wins and is reported. Red local checks always stop the merge.
When the unit reaches zero open leaves and automatic release is enabled, the loop:
deleteBranchAfterRelease allows it;With autoRelease: false, none of those release mutations starts automatically; the unit is reported release-ready. With autoRelease: "ask", the loop asks for approval for each completed unit before cutting and merging its release pull request.
pr authors the configured body, requests every configured cloud reviewer when it opens the pull request, and delegates findings and CI settlement to review.
In a draft-held repository:
baseBranches is requested only on pull requests into one of those branches, and one with requestPolicy: "final-head" is asked once, when every other finding is settled, on the head about to release CI — and again only after a fix that answered one of its own findings. A reviewer left out by its scope is announced as not requested, never waited on and never counted as failed; if the local engine then fails on a pull request no cloud reviewer was asked to review, the loop stops for a human review. Each cloud reviewer request must be proven registered — a review_requested event on the pull-request timeline — within two minutes; otherwise that reviewer is dropped for the run and reported, and nothing waits for it. review.pollTimeoutMinutes bounds only the wait for a registered reviewer’s review — and that wait is adaptive: the loop polls on a lengthening interval and stops at the reviewer’s own historical 95th-percentile latency plus a safety margin, learned per machine from GitHub’s own timestamps, never beyond the timeout, and the full timeout while fewer than five samples exist. Stopping at a learned bound is reported exactly as a timeout would be.lessonsDoc before CI.preShipChecks run against the final reviewed diff while the pull request remains held.The draft hold is conditional on review.openPullRequestsAsDraft, review.readyForReviewReleasesCi, and review.ciHeldUntilReviewSettled. When all three are false, the pull request opens ready and CI runs alongside review. Mixed values take the strictest behavior and are reported.
opened, synchronize, reopened, and ready_for_review, and the relevant jobs must be gated while the pull request is a draft. Setup detects this but never edits it; doctor reports the exact gap./devstride:create-story add a one-time export
/devstride:create-defect retry creates duplicate records
The create skills ask for a parent, board, and assignee, create the item without plan numbering or dependency edges, and invoke build-item <new-item> once, which terminates instead of selecting another item. Where the one-off lands follows Where work lands: the support train when one is configured — in which case the item gets a comment saying it ships with the next release, and stays open until the release carries the train into the base branch — otherwise its own pull request into the base branch, which is also the path for a one-off touching deploy configuration or migrations.
Use insert-story or insert-defect when the new work belongs in an existing plan and should be reached by the dependency walk.
/devstride:branch-hotfix fix-login-crash
This creates a branch from a fresh hotfixBaseBranch, so the fix does not carry unreleased development work. It does not open a pull request; build the fix, then use /devstride:pr targeting the production branch.
/devstride:release
This is separate from a release-unit pull request. It promotes release.releaseSource to release.productionBranch:
release.mergeTrainBeforeCut is true: a snapshot of the train (the live train keeps receiving one-offs) goes into the release source through its own fully reviewed pull request, CI runs once, it merges, and every one-off it carried is marked Done;release.releaseBranchPattern is set — cut the release branch from the release source, named by the pattern for today (for example release/26-10-03, then -2, -3 on a re-cut), and open the production pull request from it; otherwise the release source itself is the head;release.autoDeployOnMerge consequence;release.postDeployCheckSkill) — only after the deploy is confirmed (by release.deployVerification or by you), never before, so it measures this release rather than the previous one or an in-flight rollout; a configured check keeps that confirmation step even when no documentation or release notes run. It runs before anything public is written: PASS continues, FAIL stops for a rollback decision, NOT RUN asks you whether to proceed, and an unset key reports not configured;docs.updateSkill) so core docs are updated by default, unless suppressed, and write a release note through your local release-notes skill — only if you passed --release-notes;There is no release freeze any more (from 3.8.0). Pull requests into the release source are not settled, parked or waited for, and ci.freezeBaseWhileReleasePrReady is ignored. With a release branch, a merge into the source meanwhile simply ships in the next release; without one, it advances the release pull request’s head, and the review round runs again before the owner is asked.
A release branch is protected. Fixes found during review are new commits on it — never a rebase, amend or force-push. A fix that touches release.releaseBranchFixExclusions (deploy configuration, migrations) is refused: merge it to the release source by a normal pull request, abandon the release branch and re-cut. A hotfix that reaches production meanwhile is merged into the release branch and re-reviewed. Why a branch at all: the release source keeps moving while a release is reviewed, so cutting a branch freezes what ships without freezing everyone else’s work.
Release notes are opt-in: --release-notes (or --release-notes draft to read the note before it is published). Without the flag no note is written, and nothing in the loop decides that a release deserves one. Passing no docs or skip docs suppresses the docs phase. docs only updates documentation for an already-shipped delta; release-notes only writes the note for one.
Run /clear before moving from setup, planning or Doctor into building, reviewing or releasing. The session gate introduced in 3.5.0 keeps those job classes separate; a build loop may still deliver multiple stories in one session. See Session boundaries and deferred defects for intentional overrides.
A verified finding worth filing (a P1, a security finding, or one both likely to happen and material) that is deliberately put off is filed beside the plan in its Deferred defects container and related to the reviewed item; any other unfixed finding is dismissed with its one-line reason, and nothing is filed. They do not enter the active dependency chain, are not auto-selected, and do not hold the completed release unit open. Newly discovered implementation scope still enters the plan through insert-story.