How we ship code
Role Developer Senior developer Release owner
Flow · The trunk

The trunk

Left to right is time. The thick line is main (the only long-lived Git branch). A ticket is a short feature branch: it leaves main, gets a Preview site, then squash-merges back. GitHub Actions then updates Dev. Staging and Production are tags on one commit, later. New to this? Start on Normal way. Developer commands: Commands.

Trunk-based graph: main, short feature branches, and Dev to Production main runs left to right. A short feature branch leaves main, Preview, then squash-merges back. GitHub’s robot puts that save on Dev at once. Later the Release owner names the same save v1.5.0-rc.1 for Staging, then v1.5.0 for Production after Approve. Dev follows newest main to the right. Staging stays on the named save. Short feature branches main · the trunk Servers · Dev to Production feature/JIRA-101 Preview Developer feature/JIRA-102 Preview not merged yet · not on Staging time branch off Squash and merge Senior developer save a1b2c3d is now on main Dev follows newest main → Dev now newest main v1.4.4 · Production users had this before v1.5.0 1 · GitHub robot · auto Dev · a1b2c3d is on Dev 2 · names that ID Staging · v1.5.0-rc.1 · QA copy Release owner 3 · same save · Approve Production · v1.5.0 · users Staging and Production stay on a1b2c3d. They are not Git branches. New tickets still squash-merge to main. Dev moves right. Staging does not. Do not send newest main to Production. Users must get the Staging save.

save on main save on a feature branch squash-merge onto the trunk Dev (newest main) Staging sticker Production sticker

Preview lives on the short spur. After squash-merge, GitHub’s robot puts that save on Dev. Later the Release owner names the same save for Staging (v1.5.0-rc.1), then Production (v1.5.0 + Approve). Dev keeps following newest main. Staging stays still. There is no Git branch called develop or staging. Hotfix, rollback, and Git errors are under Exceptions.

Git vs servers

Git

  • Long-lived branch: main only
  • Short feature/… branches for tickets
  • A tag (sticker) names one commit, like v1.5.0
  • No Git branch named develop or staging

Servers (URLs the team opens)

  • Preview — website for that PR
  • Dev — newest main (Actions, at squash-merge)
  • Staging — QA, frozen on one tag
  • Production — that same commit, after Approve
Page 1 · The normal way

How we ship code

Pick the role that matches the job. Developer loop is in the green box. Copy-paste Git is on Commands. Stuck? FAQ. Something weird? Exceptions.

Developer makes a branch → Senior developer merges it → Dev updates.
Later, Release owner sends that same code to Staging (QA), then Production (users).

Developer Every ticket

This is the whole Developer job. Copy-paste is under Commands.

  1. git checkout main then git pull origin main
  2. git checkout -b feature/JIRA-101-short-name — never commit on main
  3. Edit files. git add path/to/file (not git add .). git commit. git push
  4. Open a Pull Request. Base = main
  5. Open the Preview link on the PR. Stop. Wait for a Senior developer

Words in this guide

This guideGit / GitHubMeaning
savecommitOne snapshot of the code
short IDcommit hash / SHALike a1b2c3d
stickertag + GitHub ReleaseA version name on one commit, like v1.5.0
robotGitHub ActionsDeploys to Preview, Dev, Staging, Production
replayrebaseMove the feature branch onto newest main
Preview / Dev / Staging / ProductionserversWebsites. Not Git branches

Who does what

Developer

Make a branch. Upload it. Open a Pull Request. Stop.

Senior developer

Read the PR. Press Squash and merge. Dev updates by itself.

Release owner

Name a version for QA, then send that same version to users after a required reviewer clicks Approve.

Every ticket

  1. Developer Feature branch

    Developer edits files here. Never on main.

  2. Developer Preview

    A website for this PR. Open the link on the PR.

  3. Senior developer Squash and merge

    The only merge button we use.

  4. Dev

    Always the newest main. Happens by itself.

When we ship to users

  1. Release owner The version on Dev

    Release owner writes down the short ID, like a1b2c3d.

  2. Release owner Staging · v1.5.0-rc.1

    This copy stays still. New tickets still go to Dev.

  3. Release owner Production · v1.5.0

    Must be the Staging version. Release owner waits for Approve.

Preview, Dev, Staging, and Production are servers, not Git branches. There is no branch called develop or staging.

Always

  • Developer works on a feature branch, not main
  • Developer opens a Pull Request into main
  • Senior developer presses only Squash and merge
  • Wait for a green check in Actions

Never — unless Exceptions says so

  • Developer: save files while on main
  • Senior developer: click Merge or Rebase
  • Copy files onto a server by hand
  • Release owner: send newest main to Staging or Production
Page 2 · Commands (normal way only)

Commands for each role

Developer: the daily loop is first. Copy the block that matches the numbered step. Happy path is here. Production shipped mid-ticket is the extra Developer block. Hotfix, QA bugs, and Git errors: Exceptions.

Developer Daily loop — every ticket

  1. First time on this computer: clone the repo git status should say On branch main.
  2. Get latest main, then make a feature branch Name: feature/JIRA-101-short-name. Do not save while still on main.
  3. Edit → git add the file path → git commitgit push Type the path. Do not use git add . (secrets can sneak in).
  4. Open a Pull Request. Base must be main Use the Preview link on the PR. Then stop and wait for a Senior developer.
Step 1 — first time onlygit clone https://github.com/YOUR-ORG/YOUR-REPO.git
cd YOUR-REPO
git status
Steps 2–4 — every ticketgit checkout main
git pull origin main
git checkout -b feature/JIRA-101-user-authentication

git add src/auth/login.ts
git commit -m "feat(auth): implement JWT token generation"
git push -u origin feature/JIRA-101-user-authentication

gh pr create --base main --title "feat(auth): JWT token generation"
# Or GitHub → Compare & pull request → base = main

Developer Production shipped, branch still open

Users just got a tag (example: v1.5.0). The Developer is still on feature/JIRA-101-…. That commit is already in Git. Stay on the feature branch. Rebase or merge newest main into it. If Production was a hotfix, wait until that fix is on main first (Exception 5).

  1. Stay on the feature branch. Finish or stash unsaved edits git status must say the feature branch name. Do not git checkout v1.5.0 — that leaves the ticket.
  2. Download tags and newest main. Confirm the Production save Do not copy files from the Production server.
  3. Put newest main into this feature branch, then keep working No PR comments yet: git rebase origin/main. Comments already exist: git merge origin/main. Same rule as Exception 3.
Step 1 — still on the unfinished feature branchgit status
Sample for step 1 — git statusOn branch feature/JIRA-101-user-authentication
Your branch is up to date with 'origin/feature/JIRA-101-user-authentication'.

nothing to commit, working tree clean
# If Git lists unsaved files: git add path + git commit, or git stash -u, then continue.
Step 2 — fetch. Confirm what users just gotgit fetch origin --tags
git log -1 --oneline v1.5.0
Sample for step 2 — git log -1 --oneline v1.5.0a1b2c3d feat(auth): implement JWT token generation
# That save is the Production sticker. It is already in Git. Do not copy from the server.
Step 3 — no PR comments yet: rebase onto newest maingit rebase origin/main
git push --force-with-lease
# force-with-lease updates the feature branch only — never main
Step 3 — PR already has comments: merge main in. Do not replaygit merge origin/main
git push
Then keep working on the same feature branchgit log --oneline -5
# Should show the Developer’s saves on top of a1b2c3d (and any other main work).
# Next edit: git add path → git commit → git push. Still not on main.

Senior developer Review and merge

  1. Open the PR. Base must say main If it does not, send it back. Do not merge.
  2. Wait for a green check, then read the code Checks tab, or gh pr checks 123.
  3. Click Squash and merge. Delete the branch Not Merge. Not Rebase.
  4. Open Dev and click the ticket Actions job Deploy to EC2 (dev) is green. Do not use Staging for this ticket.
Steps 1–4 — reviewgh pr checkout 123
gh pr checks 123
# GitHub: review → Squash and merge → delete branch

Release owner Staging, then Production

  1. On Dev, pick the version QA should test git log --oneline -5. Copy the first short ID (left column). Sample below.
  2. Name it v1.5.0-rc.1 and publish as a pre-release GitHub → Releases → tick Set as a pre-release. Staging updates.
  3. When QA says yes, name that same version v1.5.0 Do not pick newest main. Publish (no pre-release tick). A required reviewer clicks Approve.
Step 1 — list saves on Devgit checkout main
git pull origin main
git log --oneline -5
Sample for step 1 — git log --oneline -5 (newest first)a1b2c3d feat(auth): implement JWT token generation
7c8d9e0 fix(auth): handle expired refresh tokens
b2c3d4e feat(settings): add timezone picker
e1f2a3b chore: update composer.lock
4d5e6f7 feat(dashboard): show last login
# Copy the first ID. Here that is a1b2c3d — that is the version on Dev.
Step 2 — tag for Staginggit tag -a v1.5.0-rc.1 a1b2c3d -m "Staging candidate v1.5.0"
git push origin v1.5.0-rc.1
# GitHub → Releases → this tag → tick Pre-release → Publish
Step 3 — check the Staging savegit fetch origin --tags
git checkout v1.5.0-rc.1
git log --oneline -1
Sample for step 3 — must match Staginga1b2c3d feat(auth): implement JWT token generation
# If this ID is not a1b2c3d, stop. Do not tag Production.
Step 3 — tag for Productiongit tag -a v1.5.0 a1b2c3d -m "Production release v1.5.0"
git push origin v1.5.0
# GitHub → Releases → publish v1.5.0 (do not tick Pre-release) → Approve

QA found a bug, Production is down, or Production must go back to the last version? That is not this page — go to Exceptions.

Page 3 · Exceptions

Do not use the normal way when…

Stay on Commands unless one of these is true. Each box is: when it happens, what to do, what not to do.

Users are broken, and last good Production is not enough Need a new patch. Exception 5 (hotfix). Users are broken, and last good Production is still good Put them back. Exception 7 (rollback). QA found a bug on Staging New sticker rc.2. Exception 4. Production shipped, feature branch not finished Stay on the branch. Put newest main into it. Exception 8. Developer is on main Make a branch first. Exception 1. Merging is blocked Fix the check. Exception 2. main moved before review Replay or merge. Exception 3. About to Approve Production Staging and Production IDs must match. Exception 6.

These are not exceptions — still never

Exception 1 — Developer is on main

When
git status says On branch main and the Developer wants to save a ticket.
Who
Developer
Do this
Make a branch first: git checkout -b feature/TICKET. Then add / save / upload.
Do not
Run git commit while still on main.

Exception 2 — GitHub will not let the Senior developer merge

When
The PR says “Merging is blocked”, or the check is red.
Who
Whoever is looking at the PR (any role). This is not a fourth job.
Do this
Open the Checks tab. Fix the failing Action, or wait for a Senior developer to review.
Do not
Ask a Senior developer to merge anyway. Do not skip the green check.

Exception 3 — Someone merged to main before the Developer’s PR was reviewed

When
The Developer’s PR is still open, and main got new work.
Who
Developer (ask a Senior developer if stuck)
Do this
No comments on the PR yet: Developer replays the branch on top of newest main (paste under Fixes).
People already commented: Developer merges main into the feature branch instead. Do not replay.
Do not
Force-push main. Replay only the Developer’s feature branch.

Exception 4 — QA found a bug on Staging

When
Staging is on v1.5.0-rc.1 and QA says it is broken.
Who
Senior developer writes the fix. Release owner names the new copy.
Do this
Senior developer: new PR into main (same as a normal ticket). Then tell the Release owner.
Release owner: name v1.5.0-rc.2 and send that to Staging. Users later get rc.2, not rc.1.
Do not
Send the old rc.1 to Production. Do not pile extra unrelated tickets onto this QA copy.
Exception 4 — Release owner after the fix is on maingit checkout main
git pull origin main
git log --oneline -5
Sample: git log --oneline -5 (newest first)9f8e7d6 fix(auth): correct expiration offset
a1b2c3d feat(auth): implement JWT token generation
7c8d9e0 fix(auth): handle expired refresh tokens
b2c3d4e feat(settings): add timezone picker
e1f2a3b chore: update composer.lock
# Copy the first ID. Here that is 9f8e7d6 — the QA fix. Do not reuse a1b2c3d (rc.1).
Name the new ID for Staginggit tag -a v1.5.0-rc.2 9f8e7d6 -m "Staging candidate v1.5.0 rc.2"
git push origin v1.5.0-rc.2
# GitHub Release from v1.5.0-rc.2, tick Pre-release. Later Production uses rc.2, not rc.1.

Exception 5 — Live users are broken (hotfix)

When
Production is down, and main already has other unfinished tickets. Example: Production is v1.4.3, Staging is testing v1.5.0-rc.1.
Who
Senior developer writes the fix. Release owner sends it to users.
Do this
Senior developer starts from the live version (v1.4.3), not newest main. The patch is v1.4.4 — not v1.5.1.
Senior developer also opens a PR into main so Dev is not left buggy. Leave Staging on v1.5.0-rc.1.
Do not
Send newest main to Production (that ships unfinished work). Do not rename Staging to v1.5.1.

Paste blocks for exception 5 are under Fixes.

Exception 6 — Staging and Production are not the same version

When
The Release owner is about to click Approve on Production.
Who
Release owner
Do this
Check GitHub → Actions / Environments. Staging’s short ID and Production’s short ID must match (like both a1b2c3d).
Do not
Approve if the IDs differ. Users would get untested work.

Exception 7 — Rollback to last version

When
The new Production sticker is bad (example: v1.5.0 just went live and users are broken). The last good Production sticker still exists (example: v1.4.4).
Who
Release owner rolls Production back. Senior developer later fixes the bad release with a normal PR.
Do this
Put users back on the last good Production sticker. That is the same save as before, not newest main.
First choice: GitHub → Actions → last green Deploy to EC2 (prod) for that old sticker → Re-run jobs → Approve.
If Re-run is gone: new sticker on that same save ID (paste under Fixes). Leave Staging where it is.
Do not
Send newest main to Production. Do not write a hotfix unless last good is not good enough (that is exception 5). Do not delete tags.

Paste blocks for exception 7 are under Fixes.

Exception 8 — Production shipped, Developer’s feature branch is not finished

When
The Release owner just published a Production sticker (example: v1.5.0). The Developer is still on an unfinished feature branch.
Who
Developer
Do this
Stay on the feature branch. Fetch. Put newest main into that branch, then keep working (paste under Commands).
If Production was a hotfix, wait until that fix is on main (Exception 5), then do this.
Do not
Check out the Production sticker and keep coding. Do not copy files from the Production server. Do not git commit on main.
Page 4 · Hotfix, rollback, and Git errors

Hotfix, rollback, and Git errors

Production users broken? Use the chooser, then paste. A Git error? Use the table. If anyone is lost, stop and ping a Senior developer — do not guess with --force on main.

Need a new patch Last good Production is not enough. Exception 5 — hotfix. Put users back Last good Production is still good. Exception 7 — rollback. Git yelled, or main moved Error table and replay commands below.

If a command fails

What Git showsWhat it meansWhat to do
Please commit your changes or stash them Unsaved edits, so Git will not switch branches. Finish with git add + git commit, or git stash -u now and git stash pop later.
On branch main when the Developer meant to save a feature Exception 1. The Developer is on the shared branch. Do not save. git checkout -b feature/TICKET first.
rejected (non-fast-forward) GitHub has work the laptop does not, or the Developer replayed after already uploading. The feature branch only: git push --force-with-lease. Never force-push main.
GitHub: “Merging is blocked” Exception 2. Checks are red, or a review is missing. Open the Checks tab. Fix it, or wait for a Senior developer.
CONFLICT The same lines changed in two places. Ask a Senior developer: edit files, remove <<<<<<<, git add, then continue.
pathspec … did not match That branch or version name is not on the laptop yet. git fetch origin --tags and try again.

Developer Exception 3 — replay the branch

Only if nobody has commented on the PR yet.

Developer: replay the feature branch on top of newest maingit fetch origin
git rebase origin/main
git push --force-with-lease
# force-with-lease updates the feature branch only — never main

If people already commented: git fetch origin && git merge origin/main && git push. Do not replay.

Conflict while replaying (Senior developer helps)git status
# Edit files, remove <<<<<<< markers, then:
git add path/to/file
git rebase --continue
git push --force-with-lease

Senior developer Exception 5 — Production hotfix

Start from the live version. Example: Production v1.4.3 → patch v1.4.4.

Senior developer — write the fix from the live versiongit fetch origin --tags
git checkout -b hotfix/JIRA-999-db-leak v1.4.3
git add src/db/pool.ts
git commit -m "fix(db): resolve connection pool leak"
git push -u origin hotfix/JIRA-999-db-leak
gh pr create --base main --title "fix(db): resolve connection pool leak"
# Squash and merge into main so Dev also has the fix
# Copy the fix save ID and send it to the Release owner
Release owner — copy that one fix onto the live version, then usersgit fetch origin --tags
git checkout v1.4.3
git cherry-pick PASTE_THE_FIX_COMMIT_ID
# cherry-pick = copy that one save, without unfinished tickets
git tag -a v1.4.4 -m "Hotfix v1.4.4"
git push origin v1.4.4
# GitHub → Releases → publish v1.4.4 → Approve production

Release owner Exception 7 — Rollback to last version

Example: bad Production is v1.5.0. Last good Production is v1.4.4. Skip names with -rc — those are Staging only.

Release owner — find the last good Production stickergit fetch origin --tags
git tag --list "v*" --sort=-v:refname
Sample: git tag --list (newest name first)v1.5.0
v1.5.0-rc.1
v1.4.4
v1.4.3
v1.4.2
# Bad live sticker: v1.5.0. Last good Production: v1.4.4 (not the rc).
Confirm that sticker’s save IDgit log -1 --oneline v1.4.4
Sample: git log -1 --oneline v1.4.4c0ffee1 fix(db): resolve connection pool leak
# Users must get this save again — not newest main.
First choice — no new sticker. Re-run the last good Production deploy# GitHub → Actions
# Find the last green job: Deploy to EC2 (prod) for v1.4.4
# Re-run jobs → Approve the production environment
If Re-run is gone — new sticker on the same save as v1.4.4git fetch origin --tags
git log -1 --oneline v1.4.4
git tag -a v1.4.5 c0ffee1 -m "Rollback: restore v1.4.4"
git push origin v1.4.5
# GitHub → Releases → publish v1.4.5 (do not tick Pre-release) → Approve
# Staging can stay on v1.5.0-rc.1. Senior developer still fixes v1.5.0 on main.
FAQ · By role

FAQ by role

Find the role heading. Open the question. Paste blocks are under Commands. Unusual cases are under Exceptions.

Developer

Can the Developer commit on main?

No. main is the shared branch. Make a feature branch first, then git add / git commit / git push. If git status says On branch main, that is Exception 1.

What does the Developer name the branch?

feature/JIRA-101-short-name. Base of the Pull Request must be main.

Why not git add .?

Secrets and junk files can sneak in. Type the file path, like git add src/auth/login.ts.

Is Preview on the Developer’s computer?

No. The Developer writes code on their own computer. That is local. Preview is a website for this Pull Request. GitHub’s robot builds it after the Developer pushes the branch. Open the Preview link on the PR. It is safe to break, and it disappears when the PR is merged or closed.

Preview or Dev — which one does the Developer use?

Preview is a website for this Pull Request. Dev is newest main after a Senior developer squash-merges. The Developer stops after the PR. The Developer does not send work to Staging or Production.

When does the Developer stop?

After the Pull Request is open and Preview is checked. Then wait for a Senior developer. Do not squash-merge. Do not tag a release.

Production just shipped, and the feature branch is not finished. What now?

Stay on the feature branch. Fetch. Put newest main into it, then keep working. Do not check out the Production sticker. Do not copy files from the Production server. That is Exception 8. Paste under Commands.

Someone merged to main before this PR was reviewed. Replay or merge?

No comments on the PR yet: git rebase origin/main, then git push --force-with-lease on the feature branch only. Comments already exist: git merge origin/main. Do not rebase. Do not force-push main. That is Exception 3.

Git says Please commit your changes or stash them.

Unsaved edits are blocking a branch switch. Finish with git add + git commit, or git stash -u now and git stash pop later. See Fixes.

Senior developer

Which merge button?

Only Squash and merge. Not Merge. Not Rebase. Then delete the feature branch. Dev updates by itself.

The PR says “Merging is blocked” or the check is red.

Do not merge. Open the Checks tab. Fix the failing Action, or wait. Do not skip the green check. That is Exception 2.

Should the Senior developer open Staging to test this ticket?

No. After squash-merge, open Dev. Staging is the QA copy of a named save, later, by the Release owner.

QA found a bug on Staging. What does the Senior developer do?

Open a normal PR into main (same as any ticket). Tell the Release owner. The Release owner names v1.5.0-rc.2 for Staging. Users later get rc.2, not rc.1. Do not pile extra tickets onto that QA copy. That is Exception 4.

Production users are broken. Hotfix or rollback?

Last good Production is still good enough: Release owner rolls back (Exception 7). Last good is not enough: Senior developer writes a hotfix from the live sticker, not newest main. Example: live v1.4.3 → patch v1.4.4, not v1.5.1. Also open a PR into main so Dev is not left buggy. That is Exception 5.

A Developer is stuck on replay or a conflict.

Help on the feature branch only. Edit files, remove <<<<<<< markers, git add, then continue. Never force-push main. Paste under Fixes.

Release owner

What is a sticker?

A version name on one save, like v1.5.0 or v1.5.0-rc.1. Git calls that a tag. A GitHub Release is how the robot sees the sticker. The short ID (like a1b2c3d) is the save it points at.

May the Release owner send newest main to Staging or Production?

No. That ships unfinished tickets that landed on Dev after QA started. Pick one short ID from Dev, name it, and send that save. Paste under Commands.

What is the difference between v1.5.0-rc.1 and v1.5.0?

v1.5.0-rc.1 is a pre-release sticker. Staging (QA) gets it and stays still. v1.5.0 is the same save for users. Publish with no pre-release tick. A required reviewer clicks Approve. Production updates.

Staging’s short ID and Production’s short ID do not match.

Do not Approve. Users would get untested work. Both must be the same ID, like both a1b2c3d. That is Exception 6.

QA said Staging is broken.

Do not send rc.1 to Production. Wait for the Senior developer’s fix on main, then name v1.5.0-rc.2. Users later get rc.2. That is Exception 4.

Production is down, and main already has unfinished tickets. What version?

Patch the live sticker. Example: Production v1.4.3, Staging testing v1.5.0-rc.1 → hotfix is v1.4.4, not v1.5.1. Leave Staging on rc.1. Do not send newest main to users. That is Exception 5.

The new Production sticker is bad. Last good still exists.

Put users back on the last good Production sticker (same save, not newest main). First: GitHub → Actions → last green Deploy to EC2 (prod) for that old sticker → Re-run jobs → Approve. If Re-run is gone: new sticker on that same save ID. Leave Staging where it is. Do not delete tags. That is Exception 7.

May anyone copy files onto a server from a laptop?

No. GitHub’s robot copies code to Preview, Dev, Staging, and Production. There is no Git branch called develop or staging.