On this page you will follow finished work the rest of the way: from an Epic's branch or the support train into develop, from develop into a production release, and then check that production is running it and is healthy.
develop is treated as productiondevelop is cut into a production release several times a day, and merging a release into master deploys production automatically. Whatever is on develop when the next release is cut goes to customers, usually within hours. There is no quiet period in which to finish or tidy something up.
So nothing merges into develop unless it is complete and safe to go live that afternoon. That is why Stories collect on their Epic's own branch, where they are allowed to be unfinished, and only a finished, fully reviewed Epic merges into develop. Before an Epic merges:
How DevStride Is Built, and Why tells the story behind these rules.
Story ─ merge ─▶ its Epic's branch ─ Epic release pull request ─▶ develop
One-off ─ merge ─▶ train/support ─ train pull request, at each release ─▶ develop
develop ─ cut ──▶ release/YY-MM-DD ─ release pull request + owner's yes ─▶ master
Seed deploys every merge into develop to the dev stage (a stage is a separate, fully deployed copy of the application), and every merge into master to production.
developAn Epic's Stories merge onto its integration branch (named <you>/<YY-MM-DD>/epic-<I#####>-<slug>, where I##### is the Epic's item number) without pull requests of their own; Develop covers that part. When the last one lands:
epicIntegrationBranches.autoRelease is false in .claude/ds-config.json, by the owner's decision, so a finished Epic waits until the owner says to release it.develop into the branch: a merge, never a rebase, because the branch's commits are already shared on GitHub.develop as a draft, through /devstride:pr. The description leads with the Epic and lists its Stories. While a pull request is a draft, CI (the automated checks GitHub runs) waits, apart from a few cheap policy checks such as merge-path (below).develop; it first sees this code on the production release. Every finding is checked, then fixed or dismissed with a reason, and every review thread is answered and resolved. Two rounds is the normal target; a confirmed serious problem keeps review going until a round finds none.local-ci. Before the pull request is marked ready, this runs on the machine:ds verify full --post --adopt-release
packages/mcp, the tool server that lets AI agents work with DevStride) and of the frontend, the UI build and the customer portal's size budget, the whole backend test suite apart from the golden dataset's own tests (the golden dataset is the generated demo data), and any check the changed paths trigger. A change to stacks/** or deploy configuration, for example, also runs the infra-synthesis check, because Seed deploys develop on every merge and a broken stack would break the shared dev deploy. With --post it marks the pull request's head commit (the latest commit on its branch) local-ci pending while it runs, then success or failure. It refuses to start on a tree with uncommitted changes, and if the head commit or the files change during the run it posts no result at all. (--adopt-release only matters for the sync after a release, below.)develop that is a short list: ci-policy (which checks the draft-first convention), the MCP tests when packages/mcp changed, and the backend's quick gate job. The backend test suite, the linters and the UI tests report skipped here, which still satisfies them; they run on the release into master.develop to the dev stage.On the Epic's release pull request: merge-path green from the moment it opens (it runs on drafts); the pull request a draft until review settles; local-ci green before it is marked ready; then Backend Tests, the linters and the UI tests skipped. develop requires merge-path and local-ci before anything merges.
develop: the merge-path checkdevelop reaches production at the next release, so only branches that have been through a full review may merge into it. The required merge-path check admits these heads, and nothing else:
| Head branch | What it is |
|---|---|
<you>/<YY-MM-DD>/epic-<I#####>-<slug> | an Epic's integration branch |
train/support, or the snapshot a release cuts from it | the support train (below) |
| a master-into-develop sync branch | the close-out of a release, below |
release/… | a release branch |
<you>/hotfix/… | a hotfix branch |
dependabot/… | a Dependabot dependency update |
The rules are mergePath.allowedHeadPatterns in .claude/ds-config.json. The check runs on drafts too, and reads its rules from develop itself, so a pull request cannot loosen its own gate.
Any other head fails unless the pull request carries the merge-path-override label. That is for a change that must ship on its own pull request (deploy configuration or migrations, below) or an Epic branch named before the epic- marker existed. Nothing adds the label for you:
gh pr edit <number> --add-label merge-path-override
To know before you open the pull request, run the same checker locally. It prints ALLOWED or BLOCKED with the reason:
ds merge-path check --head <branch> --base develop
ds merge-path check --head <branch> --base develop --labels merge-path-override
See Command Reference: ds merge-path.
A one-off is a Story or Defect outside any plan, usually made with /devstride:create-story or /devstride:create-defect: it has no [N] number and no dependency links. Even when it is filed under an Epic, it never goes on that Epic's branch (with ds work start, pass --one-off for one filed under an Epic). It does not get a pull request of its own. After a short risk check and a green ds verify land (the landing check every item passes), it merges onto the support train, the long-lived branch train/support. Then:
develop first. /devstride:release takes a snapshot of the train (the live train keeps accepting one-offs) and opens a pull request from it into develop. That pull request gets the full Claude and Codex review over the whole train, the local-ci check and one CI run: the one-offs' first full review. It merges before the release is cut.develop straight after its pull request merges, and again when the release closes out. The release does this for you; ds work train rotate does it by hand once a release has carried everything on the train.To see what is waiting, run ds work train status. It lists the one-offs on the train that have not reached develop yet, and changes nothing.
Deploy configuration and migrations never ride the train. A one-off that changes stacks/, sst.config.ts or the database migrations under backend/src/libs/infrastructure/database/migrations/ would reach production soon after the train merges. Instead it ships to develop by its own pull request, so the dev stage deploys it before any release carries it. The delivery loop routes such a change this way by itself; by hand, start it with ds work start <I#####> --infra and open its pull request with /devstride:pr. Either way, nothing adds the merge-path-override label for you: add it with gh pr edit <number> --add-label merge-path-override. The pull request then gets the full review and local-ci like any other pull request into develop. Develop has the commands in context.
/devstride:releaseThe owner decides when to release. Running the command prepares a release; it never counts as permission to deploy.
Run it from a leased wt1 or wt2 checkout (see Develop), never the main checkout, and keep the lease until the release's very last step hands the checkout back.
/devstride:release
develop and master in the last few hours, and says so if another session seems to be active.develop, as above.develop that are not yet in master, and which of them a user would notice.release/YY-MM-DD from develop (with -2, -3 added when a release is re-cut), and opens the release pull request from it into master as a draft. develop keeps accepting merges meanwhile; they ship in the next release.master. Copilot is asked once, on the final reviewed version, and again only after a fix that answered one of its findings.ds golden release check: the release will not go ahead without a prepared, certified copy of the demo dataset for exactly the code being released. The Golden Dataset page explains the golden source.master that lands the release reuses this green run when the code is identical, so the suite does not run twice.release.autoDeployOnMerge: "Merging to master auto-deploys production via SEED (seed.run) — no manual deploy step."ds-update-docs skill brings docs.devstride.com in line with what shipped, by default, only once the deploy is confirmed live and healthy. Add no docs to the command to skip it./devstride:release --release-notes publishes one after the deploy is confirmed, and --release-notes draft leaves it for the owner to read first. Nothing in the loop decides on its own that a release deserves a note; the owner does.master back into develop by its own pull request, so fixes made on the release branch reach develop; that pull request's local-ci reuses the release's green cloud run in seconds when the code is identical (--adopt-release). It deletes the release branch once both branches contain it, and brings the support train up to date.A release branch is protected. Fixes found during review are new commits on it, never a rebase or force-push; GitHub refuses a force-push to any release/* branch. A fix that touches deploy configuration or migrations is refused there: merge it into develop through a normal pull request, abandon the release branch, and run /devstride:release again to re-cut. If a hotfix reaches master while a release is open, the release merges master into its branch and reviews it again, and asks the owner again.
The Delivery Loop: Production release has the full mechanics, and Configuration Reference: Production release has the settings.
Seed (seed.run), a hosted deploy service, deploys this application. A merge into develop deploys the dev stage (app.devstride.dev), and a merge into master deploys production, the prod stage (app.devstride.com). GitHub Actions deploy nothing, and there is no deploy step to run by hand: merging is the deploy.
During each deploy Seed runs the additive migrations before the new code goes live and the tightening ones after it. Deployment explains this and the checks that run in GitHub Actions. An ordinary production deploy took about seventeen minutes from start to finish when it was measured.
Seed writes a GitHub deployment record for every commit it deploys, under the environment prod or dev, created by its own GitHub account, seed-deploy[bot]. Read those records with gh, the GitHub command-line tool; it needs to be signed in.
To see which commit production is running, run this from the repository's root:
REPO=devstride/devstride bash .github/scripts/resolve-live-production-sha.sh
It prints the full commit id. It trusts only records made by Seed's own account whose newest status is a success, so a record anyone else wrote cannot fool it. It exits with status 1 when it finds no deployment Seed proved successful, and 2 when GitHub's API failed or was rate-limited. In both cases the answer is "unknown", never "not live".
To check that your change is part of what production runs:
git fetch origin master
git merge-base --is-ancestor <your commit> <the commit it printed>
Exit status 0 means production's commit contains yours.
To see the latest deploys of a stage, newest first:
gh api --paginate 'repos/devstride/devstride/deployments?environment=prod&per_page=100' \
--jq '.[] | select(.creator.login=="seed-deploy[bot]") | [.sha[0:9], .created_at] | @tsv' | head -5
Use environment=dev for the dev stage. Each line is a commit Seed started deploying and when it started. A record on its own does not prove the deploy finished; for production, the script above checks that.
After the owner confirms the deploy, /devstride:release runs the repository's post-deploy check, the ds-post-deploy-release-check skill (named in .claude/ds-config.json as release.postDeployCheckSkill). It does three things:
ds-post-deploy-health skill:Its first line is exactly one of POST-DEPLOY HEALTH: PASS, POST-DEPLOY HEALTH: FAIL or POST-DEPLOY HEALTH: NOT RUN, followed by the evidence, each line naming the command that produced it. PASS lets the release go on to documentation. FAIL stops it for the owner's rollback decision; a failure only in publishing the golden source is repaired and retried instead, and never calls for rolling the application back. NOT RUN means the check could not be done, for example because a sign-in was missing or a service could not be reached, and the owner is asked whether to proceed. Missing credentials are never a PASS.
You can also run the health check by hand after any production deploy: ask Claude Code to run the ds-post-deploy-health skill for the merge commit. It needs a valid AWS sign-in to the production account and a signed-in gh; Credentials and access covers both.
An alarm that fires soon after a deploy is not proof the deploy caused it. Before blaming the release, look at when the alarm's underlying metric first went over its threshold; it may have started before the deploy.
ds seed deployNormal releases never need a deploy command, because merging is what deploys. For a deliberate redeploy of a chosen commit, ds seed deploy asks Seed to deploy it to a stage:
ds seed deploy --stage <stage> --commit <commit id>
ds seed deploy --stage <stage> --commit <commit id> --force
--force deploys even when Seed finds no changes. The command sends the same request as Seed's own command-line tool, for DevStride's Seed organization and app (--org and --app default to devstride). It needs no AWS sign-in. It uses the team's shared Seed token, SEED_TOKEN, from your machine's agent credentials file (~/.config/devstride/agent.env, written by ds secrets pull), and never prints it.
ds seed deploy does not guard any stage. --stage prod starts deploying that commit to production straight away, without any of the review, CI or owner approval a release goes through. Never run it against prod without the owner's explicit yes for that specific deploy.If the token has not been added yet, the command refuses and explains how to add it: the owner makes a token in Seed's organization settings and stores it with pbpaste | ds secrets set agent SEED_TOKEN, and each machine then runs ds secrets pull. Credentials and access has the full map of keys.
A hotfix is an urgent fix that must reach production without the unreleased work on develop. Run /devstride:branch-hotfix I#####-<short-slug> on a clean tree: it reminds you to stop any running dev server, cuts <you>/hotfix/<YY-MM-DD>/I#####-<short-slug> from a freshly pulled master, pushes it, and resets this checkout's local database so it matches production's older code. Build the fix, then open its pull request into master with /devstride:pr. It gets the draft-first treatment, with Claude, Codex and Copilot reviewing, then CI, including the backend test suite, because this code has not been tested before. Open it only against master, never against develop as well: a branch open against both can hide a failing check. /devstride:pr stops when the pull request is reviewed and green; it does not merge it. Merging into master deploys production, so that merge is the owner's call. If a release is open when the hotfix lands, the release merges it into its own branch and reviews it again. Like everything on master, the hotfix comes back to develop through the master-into-develop sync at the end of each release.
merge-path is red. Run ds merge-path check --head <branch> --base develop for the reason. A one-off belongs on the train, and only an infrastructure one-off or an old-style Epic branch should carry merge-path-override.local-ci is missing, failed, or stuck at pending. Re-run ds verify full --post on a clean tree at the pull request's head; a run whose head or files changed part-way posts nothing, so its pending mark stays until a later run replaces it. It needs this checkout's test containers, and only one runs per machine at a time. The full log is .ds/verify-full.log.develop by a normal pull request, abandon the release branch and re-cut.ds-alarm skill; ds-incident-response is the full on-call protocol.aws sso login --profile <profile>; see Your AWS sign-in has lapsed) or gh auth login. Run it, then run the check again. A deploy still rolling out after 25 minutes is also NOT RUN: check again once it finishes.Anything else: Troubleshooting.
Next: Troubleshooting, for when a step on any page does not go as described.
Develop
From a READY machine to a story merged onto its target: take a checkout, start the item on the right branch, build and try it, check it locally, and land it.
Troubleshooting
What you see, why it happens and what to do, for the problems a DevStride machine or session meets most: sign-ins, Docker, credentials, demo data, machine identities, test runs and the logs to read.