Purpose
The default agent behavior is to treat "ship it" as one undifferentiated act — git add -A && git commit -m "fix" && git push — and to treat reviewer comments and red CI as someone else's problem.
Each shortcut fails silently in a way nobody notices until later: add -A sweeps a secret or an
unrelated edit into history; a one-word commit message erases the why the next reader needs; a PR
with no verification steps makes the reviewer reconstruct your intent from the diff; a blind CI
re-run burns minutes and hides a real bug; parallel branches in one checkout overwrite each other's
work. This guide makes shipping a disciplined lifecycle — stage, commit, push, open, then close
the loop on review and CI — so the change lands the way you meant it to.
The ship loop here is stage → commit → push → open a PR, plus address review comments and fix a failing CI job; the parallel-work isolation uses git worktrees.
Resolve the repo's flow first
Before committing, check the repo for a contribution guide (CONTRIBUTING.md, AGENTS.md, a PR
template, or the CI config). If it prescribes a branch-naming scheme, a commit-message convention
(Conventional Commits, a sign-off, an issue-key prefix), or a required PR section, follow that over
the generic flow below — a house convention beats a guessed one. Also confirm whether to commit or
push at all: do those only when the user asked, and never push straight to the default branch.
The ship lifecycle
1. Stage only what belongs
Run git status and git diff and read them before staging. Stage the files that make up this
change by name (git add path/...), not git add -A / git add . — the catch-all is how a .env,
a credential, a debug print, or an unrelated edit ends up in the commit. After staging, run git diff --cached and read the whole thing: that is exactly what you are about to commit. A change that
mixes two unrelated concerns should be two commits.
2. Commit with a message the next reader needs
- Subject: imperative mood, ~50 chars, no trailing period — "Add retry to the upload client", as if completing "If applied, this commit will…". It describes what the change does, not what you did ("Added", "Fixes").
- Body (the part that matters): explain the why — the problem, the constraint, the alternative you rejected. The diff already shows what changed; the body carries the intent the diff cannot. Wrap at ~72 chars, separated from the subject by a blank line. Skip the body only for a genuinely trivial change.
3. Push to a branch, never force a shared one
Work on a feature branch, not the default branch. Push with git push -u origin <branch>. If you
must rewrite history you already pushed, use git push --force-with-lease, never bare --force:
--force-with-lease refuses the push if someone else added commits you have not seen, which is the
exact accident bare --force causes. On a branch others may be using, prefer not to rewrite history
at all.
4. Open a PR that states intent and how to verify
The description has two non-negotiable parts: what this changes and why (the intent, linked to the issue/requirement it satisfies), and how to verify (the exact commands or steps a reviewer runs to confirm it works — the test command, the manual repro, the before/after). A PR with no how-to-verify forces the reviewer to invent a check or rubber-stamp the diff. Paste the verification output you already ran into the description.
Close the loop on review comments
When a PR has review feedback, do not cherry-pick the easy ones and leave the rest dangling. Pull
the unresolved comments (gh pr view <n> --comments, or the review threads via the API), and
for each one: act on it or reply with why you are not, then resolve the thread. The loop is closed
only when every thread is either addressed-in-a-commit or answered — a reviewer who has to re-check
whether their three comments were seen has been given more work, not less. Push the follow-up commit
and leave a short comment pointing to it.
Fix failing CI by reading it, not retrying it
A red CI job is a result to read, not a button to mash. Read the failing job's logs first (gh run view <run-id> --log-failed), find the actual failing step and its error. Then reproduce it
locally — run the same command the job ran — so you are fixing the cause, not guessing. Fix it,
re-run that command locally to confirm green, then push. Re-running the CI job without a change is a
blind retry: if it greens, you have shipped a flake or a load-dependent bug; if it fails again, you
have learned nothing and spent the minutes anyway. Retry-without-a-change is only ever justified for
a known-infrastructure outage, and then say so.
Isolate parallel work in separate worktrees
When you have more than one line of work in flight (a feature branch and an urgent fix, or two
independent tasks), do not stash-and-switch branches in a single checkout — that serializes the
work and risks carrying uncommitted state across branches. Use a separate git worktree per
branch: git worktree add ../repo-feature-x feature-x gives each branch its own directory sharing
one .git, so builds, test runs, and edits never clobber each other. Remove it when done with git worktree remove ../repo-feature-x. The deeper worktree workflow (when to spin one up, naming,
cleanup discipline) is in references/worktrees.md.
Gotchas
--forceclobbering a shared branch. A bare force-push silently discards commits a teammate pushed while you were rebasing — they are simply gone from the remote, no warning. Use--force-with-lease, which aborts when the remote moved under you.- Secrets and stray files swept in by
add -A. The catch-all stages everything dirty in the tree — a generated.env, a credential, a scratch file, an unrelated edit. It commits clean and pushes clean; the leak surfaces only when someone reads the history. Stage by name and readgit diff --cached. - Blind CI retry that "fixes" a flake. Re-running a red job until it greens hides a real
intermittent bug behind a passing badge — the failure is load- or order-dependent and will return
in production. Read the log; reproduce locally. (A flaky test is its own job — pair with
fix-flaky-test.) - Dangling worktrees. A worktree you forgot to remove keeps a stale branch checked out and its
build artifacts on disk; later
git worktree addfor the same branch errors, or you edit the wrong copy thinking it is the live one. List withgit worktree list; prune withgit worktree remove/git worktree prune. - PR description that restates the diff. "Updated upload.ts" tells the reviewer nothing the file list already showed — the intent (why) and the verification (how to check) are the parts only you can supply, and the parts the reviewer actually needs.
- Review thread left open after the fix. You fixed the issue in a commit but never replied or resolved the thread, so the reviewer cannot tell addressed from ignored and re-reviews everything. Closing the loop is part of the fix.
What does not belong
git add -A/git add .without reading the staged diff; baregit push --forceon any branch others touch; pushing to the default branch directly; committing when the user did not ask to.- A commit subject that says what you did ("Added X") rather than what the commit does ("Add X"); a body that paraphrases the diff instead of giving the why.
- A PR with no how-to-verify; review comments addressed silently with no reply/resolve; a CI re-run with no code change behind it.
Bundled resources
references/worktrees.md— the full git-worktree workflow for parallel branches: when to create one, naming and placement, running tools inside it, and the cleanup discipline that keeps worktrees from dangling.