On this page you take one item from the plan to a merge. You reserve a checkout, start the item on the branch it belongs to, build and try it against demo data, check it on your own machine, and land it.
It assumes two things: Set up your machine ended with the one-word verdict READY, and the item already exists in DevStride (Plan work covers making it). Item numbers below are written I#####: use the real number from DevStride.
There are two ways to drive the work, and both follow the same rules:
/devstride:build-item. It picks the item, builds it, reviews it, checks it and merges it.ds work start and ds work land, put it on the right branch and check it before it merges. Codex (OpenAI's command-line coding agent) uses the same two commands.develop is effectively production. It is released to customers several times a day, so nothing merges into it until it is finished and fully reviewed.develop in one reviewed pull request.The rest of this page walks those rules in order. How DevStride Is Built, and Why explains the reasoning behind each one.
Each machine has exactly three copies of the repository, called checkouts. Each has its own copy of the app and its own database in Docker, so several sessions can work side by side.
| Checkout | Folder | Who works there |
|---|---|---|
main | ~/dev/devstride | The person at the machine |
wt1 | ~/dev/devstride/.worktrees/wt1 | Agent sessions |
wt2 | ~/dev/devstride/.worktrees/wt2 | Agent sessions |
Never make another copy (git worktree add, ds worktree create) unless the owner asks for one. When all three are busy, wait, or ask the session that holds one. Extra copies were made "to be safe" in the past, and nobody ever cleaned them up.
You take a checkout with a lease. A lease is a small file in the checkout's own git folder that records who holds it and why. It never shows up as a change in your work. You hold it until you hand the checkout back.
Why a lease rather than a quick look: a clean folder does not mean nobody is using it. A release can sit in one for hours, another session may be running tests there, and two sessions can both see "clean" before either one switches branch. Only one session can create the lease file, so only one can win.
--session <name> to every ds pool and ds work command):export DEVSTRIDE_SESSION="<your name>"
ds pool status
main. The extra flag is there to keep agents out of main, which belongs to the person at the machine:ds pool checkout main --purpose I##### --i-know-main-is-free
main without a lease. The lease matters for ds work, which only runs in a checkout you hold. Take it while main is clean and on develop, before you start the item (see IN USE below).wt1 or wt2 itself, from inside the session (AGENTS.md, the rules every agent reads, tells it to):ds pool checkout wt1 --purpose I#####
ds pool checkout prints exactly one outcome:
| Outcome | What it means | What to do |
|---|---|---|
LEASED | The checkout is yours. | Carry on. Running checkout again on a checkout you already hold changes nothing and says so. |
BUSY | Another session holds it. The line names that session and its purpose, and says you do not hold it, even when the other session uses your name. | Take another checkout, or ask that session. Never delete someone else's lease. |
IN USE | Nobody holds a lease, but the checkout has uncommitted changes or is on someone's branch, so a session from before leases existed may be working there. Your lease has already been released. | Take another checkout. |
BUSY and IN USE exit with code 2. A checkout must be clean, and on develop or detached (on no branch at all), before you can lease it. That is the state ds pool checkin leaves it in.
From a plain terminal, one that is not a Claude Code or Codex session, checkout also prints an export DEVSTRIDE_LEASE_TOKEN=… line. Run it in that terminal. Later ds pool and ds work commands use the token to prove they come from the session that took the lease; without it they are refused as someone else. Claude Code and Codex sessions need no token: each command in an agent session carries that session's own id.
Before it hands the checkout over, ds pool checkout brings it up to date. It moves a parked checkout to the latest develop, reinstalls dependencies, and brings its demo data up to date. It reports anything it may not fix itself, such as newer secrets (run ds secrets pull for those). Then it starts the checkout's app.
While you hold it, switch branches freely. The lease, not the branch, makes the checkout yours.
The checkout pool covers the rest: customer copies, cloud mode and every flag.
You never work out the target branch by hand: /devstride:build-item and ds work start both compute it from the item's place in DevStride, and a test holds the two to the same answer. This is the rule they follow:
<you>/<YY-MM-DD>/epic-<I#####>-<slug>, and is created from develop the first time one of the Epic's Stories starts.train/support. Every release first merges the train into develop through its own reviewed pull request.stacks/, sst.config.ts, or the migrations folder) never rides the train. It goes to develop by its own pull request instead. Why: the train reaches production soon after it merges, and such a change deserves its own cloud checks and an earlier deploy to the shared development environment.Nothing lands on develop or master directly.
Branch names. A feature branch is <you>/<YY-MM-DD>/<I#####>-<slug>, for example jane/26-10-06/I#####-retry-failed-export. <you> is the first word of your git config user.name, lowercased. The date puts the year first so branches sort by date in any plain listing. Some older branches put the month first; they are mistakes, not a pattern to copy. The tools name branches for you.
Where work lands has the full rule.
Open a Claude Code session in wt1 or wt2 (for example cd ~/dev/devstride/.worktrees/wt1 && claude). The session takes that checkout's lease before it builds. Build one named item:
/devstride:build-item I#####
Or pass a plan's top item, and it builds the next unblocked item under it (the highest-priority one whose blockers are all Done):
/devstride:build-item I#####
Once you trust one pass, let it walk the whole plan, one item after another:
/loop /devstride:build-item I#####
build-item fetches the latest code, picks the item, moves it to In Progress, branches, builds, runs its risk check, runs the local checks, merges, and records on the item what actually shipped. Its DevStride changes are live: status changes, comments and dates reach your real organization straight away. If this session was just used for planning, run /clear first: the plugin keeps planning and building in separate sessions. The Delivery Loop describes every step.
In the checkout you leased, with a clean tree:
ds work start I#####
It reads the item and everything above it from DevStride, picks the target by the rule in step 2, creates the Epic's branch on GitHub if it does not exist yet, and cuts your feature branch from a fresh copy of the target. It prints the branch it made, the target it chose and why. It needs no AWS sign-in: it reads DevStride with the agents' own key.
| Option | Use it when |
|---|---|
--dry-run | You want to see the branch and target first. Nothing changes. |
--slug <kebab-case> | You want a different name after the item number. By default it comes from the item's title. |
--one-off | The item sits under an Epic but is really a one-off (an inbound request filed there). It goes to the support train, never to that Epic's branch. |
--infra | The one-off changes deploy configuration or migrations. The branch is cut from develop, to ship by its own pull request (step 6). |
--target <branch> | start stopped and asked: for example, the Epic already has an integration branch from before the epic- naming. |
It starts only a Story or a Defect, one at a time, and refuses a checkout you have not leased.
ds work never changes the DevStride item. Move it to In Progress yourself when you start, and record on it what you shipped.
The app. Each checkout's app runs that checkout's code in Docker. ds worktree ls prints each checkout's address; main is at http://localhost:8080. No AWS sign-in is needed for any of it. After the Mac restarts, start Docker Desktop, then ds worktree up main starts main's app again.
Docker is the local path. When you need the real AWS services (queues, events, email, file storage), run a checkout against your machine's own AWS stage, which setup built: ds pool cloud wt1 runs wt1's code against it, and ds pool cloud wt1 --off goes back to Docker. One checkout per machine at a time. Your AWS stage explains it.
Demo data. Every checkout's database holds the Acme demo organization (the golden dataset), which setup built once and copied into each checkout. Sign in with the username acme.devstride (a username, not an email) and the password Demo@123. The same password works for the other demo people, such as alex.rte.
Reset the demo data in seconds. In a checkout you hold:
ds pool reset wt1
It copies the machine's demo-data snapshot over the checkout's database and re-creates the demo logins. If your branch adds migrations the snapshot lacks, it runs them and checks each one landed. If your branch is behind the snapshot, it refuses, because nothing moves a database backwards. Why: a full rebuild of the demo data takes minutes; a reset takes seconds. If the demo data itself is wrong, fix the generator rather than the rows; see Golden Dataset.
Database changes. After you add a migration, apply it to your checkout's own database with ds worktree cli <checkout> migrations run (for example ds worktree cli wt1 migrations run). Every migration must be safe to run twice and safe while old and new code run side by side; AGENTS.md, "Migration Safety", has the rules.
Generated types. The backend type-check needs the types that SST (the framework that deploys the backend to AWS) generates, which live in .sst/types and are never committed. Each checkout makes its own: ds verify land, ds verify full, the pre-push hook and setup all run cli/ensure_sst_types.sh first, which regenerates them in about five seconds, with no AWS sign-in, whenever they are missing or were made from different stack files. After you change stacks/ or sst.config.ts, the next of those runs catches up; to do it now, run sh cli/ensure_sst_types.sh.
Run one test with Vitest, the test runner, from the repository's top folder:
pnpm --dir backend exec vitest run tests/suits/path/to/test.spec.ts
test script has no --run, so pnpm test -- <path> does not run one file. It starts the whole suite in watch mode and never exits, with no output at all, which looks like a slow test rather than a wrong command. Always include vitest run, and use --dir backend from the top folder: there is no vitest configuration at the top level.Tests run against this checkout's own test databases in Docker. Never run two test runs against the same checkout at once: they corrupt each other's databases, so the second one is stopped.
Changed an API route? Regenerate the API client and the MCP files on the host, never through ds worktree cli, and check the generated files rather than the exit code. API Development explains why; the repository's ds-api-regen skill has the commands.
Before anything lands, it passes the landing check:
ds verify land
For the files your change touches, it:
packages/mcp changed, and the extra checks a changed path triggers (for example the golden-dataset assertions when the generator changes).It prints one PASS or FAIL line per step and stops at the first failure. Its whole output goes to .ds/verify-land.log in the checkout, and a receipt to .ds/verify-land.json. ds verify land --dry-run lists the steps without running them.
What it needs, and what it does not:
By default it compares your branch with the target ds work start recorded, otherwise with whichever of develop, the support train and the Epic branches your branch is closest to. --base <ref> sets it explicitly.
ds verify full is the bigger check: on this machine, everything cloud CI runs on a release, including the whole backend suite and the UI build. It takes about half an hour. With --post it records its result as the local-ci status on the exact commit it checked, and develop will not accept a pull request without a green one. The loop runs it on each pull request into develop before that pull request is marked ready; you rarely run it for a single Story. See local-ci.
A Story on an Epic branch, or a one-off on the support train, gets no pull request of its own. Once its checks pass, it is merged onto its target on your machine and pushed. The full review and CI are not skipped: they move to the release unit's pull request (the Epic's pull request into develop, or the train's at the next release), where they run once over the whole batch. This is fast develop mode.
build-item lands the item itself. Before it merges one, it needs:
ds verify land).Then it merges the item's branch onto the target with a merge commit, pushes, and deletes the item's branch.
On your feature branch, with everything committed:
ds work land
It:
ds verify land against the target. Its output goes to .ds/work-land.log; a failure lands nothing;It never pushes to develop, master or any other protected branch, and never forces a push. When it finishes, it says when the item ships:
develop, and is Done then.develop. The release marks it Done.Two refusals to know about:
develop and open its own pull request. Next time, start such a change with ds work start <I#####> --infra.--infra is never landed by ds work land. Open its pull request with /devstride:pr in Claude Code (it opens a draft and runs the full review), then add the label that lets it into develop: gh pr edit <number> --add-label merge-path-override. ds merge-path check --head <branch> --base develop tells you beforehand whether a branch may open a pull request into develop.--no-verify skips the landing check, with a warning. It is refused for the support train, because nothing else checks that code before the train's pull request.
When your work has landed or been handed over, stop every process you started in the checkout (test runs, dev servers, watchers), then:
ds pool checkin wt1
It refuses unless you hold the lease and the tree is clean. It stops the checkout's app (its test databases keep running), leaves it detached at a freshly fetched develop, and removes the lease. Its data is kept. Handing main back stops main's app too; ds worktree up main starts it again.
When an Epic's last Story has landed, the Epic is release-ready. In this repository its pull request into develop waits for the owner to cut it; Release covers that step and the release to production. What happens on every pull request is the same:
/devstride:pr opens it that way. A pull request a person opens straight to ready fails the ci-policy check (the check that enforces drafts first).master (the release, or a hotfix). It is asked once, on the final version, and again only if a fix answers one of its own findings.develop. merge-path admits only an Epic branch, the train, a release or hotfix branch, a sync from master or a Dependabot update, unless the pull request carries merge-path-override. local-ci is the full local check from step 5. The heaviest cloud checks (the backend suite, the linters and the UI tests) report skipped there and run once per release, on the pull request into master.Because fast develop mode skips the per-Story pull request, the Epic's pull request is the first time any outside reviewer or CI sees that code, so it is reviewed in full, every Story included. The Delivery Loop has the mechanics.
Every Claude Code and Codex session in the repository runs a small check before each shell command. It refuses the commands that would skip the flow above:
develop or master, in any form;release/* branch;git merge while you are on develop or master;gh pr merge into develop unless the merge-path rule admits the branch, and into master unless it is a release or hotfix branch;gh api writes to GitHub's branch rules.When a session starts, the same check prints a short banner: the checkout you hold, where your branch lands, and any open release.
It is a speed bump, not a lock. It fails open: when it cannot run (no Python, a command it cannot read), the command goes through and the reason is written to ~/.cache/devstride/merge-guard.log. It only sees the command line, so a script or GitHub's web page gets past it. The real boundary is on GitHub: the required checks and branch rules.
DEVSTRIDE_MERGE_GUARD=0 turns the guard off, and is never the way past a refusal: the block is the point. Ask the owner. If you think a refusal is wrong, ds merge-path check explains the rule's verdict. The merge guard has the full list.ds pool checkout printed LEASED, and ds pool status shows the checkout as yours.ds work start (or build-item) named your feature branch and its target: the Epic's branch or train/support.ds verify land printed PASS for every step.ds work land (or build-item) said the item landed, and when it ships.ds pool checkin handed the checkout back, clean and parked at develop.ds verify land keeps waiting: Another ds verify run is going.ds pool status says a checkout's data is not recorded: A checkout without its demo data.For everything else, start at Troubleshooting.
build-item does at each step, and the pull-request pathds pool, ds work and ds verifyNext: Release
Plan work
Turn a piece of work into a DevStride plan the delivery loop can build one item at a time: how work is organized here, dependency links, delivery profiles, and the planning commands.
Release
How finished work reaches develop and then production: an Epic's release pull request, the support train, /devstride:release, Seed's deploys, the post-deploy check, and how to tell whether a commit is live.