PR Workflow โ
Date: 2026-04-30 Status: โ Active
Overview โ
This guide defines how work is structured into branches, worktrees, and pull requests in the Lantern repo. It exists alongside CHANGELOG_WORKFLOW.md (what to record per merge) and AUTOMATED_COMMITS.md (commit-message conventions).
Read this before starting any multi-step initiative or when in doubt about whether to split work into multiple PRs.
Kickoff decision โ
Before creating a branch or touching code for a new feature, ask one explicit setup question that also confirms branch setup:
Do you want this work in the main checkout or in a git worktree, and should I create a fresh branch from
devfor it?
Default recommendation: main checkout for sequential work on one feature. Worktrees are opt-in when the user wants isolation or true parallel branch work. Do not create a worktree just because the task is a feature.
Branch-name convention โ
<type>/<issue>-<short-kebab-topic>| Pattern | Use |
|---|---|
feat/<N>-<topic> | New feature tied to issue #N |
fix/<N>-<topic> | Bug fix tied to issue #N |
chore/<topic> | Chores (issue number optional) |
docs/<topic> | Docs-only changes |
refactor/<N>-<topic> | Refactors |
revert/<topic> | Reverts |
Examples:
feat/298-release-pipelinefix/348-skip-logicfix/337-followup-assistant-deploychore/preview-label-gate
Why include the issue number: enables gh pr list --search "head:feat/298-*" filtering, and issue-automation.yml reads branch names to set issue status. The convention is a default โ hot-fixes / spike branches can deviate when needed.
Single-PR default โ
Default to one PR per session/issue/theme, even when the work spans multiple commits or could technically split.
Multiple PRs create:
- Out-of-order merge risk
- Repeated review cycles
- Fragile inter-PR ordering ("land #N first to save preview spend")
- Dangling branches
The structure underneath the PR can be whatever serves the work:
- Single branch + multiple logical commits (simplest, default)
- Multiple feature branches โ merged into integration branch โ one PR (when parallel work actually helps)
Only split into multiple PRs when:
- The work is genuinely independent and would block on a different reviewer / timeline
- A reviewer explicitly asks for a split during review
- Two unrelated things accidentally landed on the same branch
When in doubt, keep it as one PR.
Issue โ PR mapping โ
When multiple issues fold into one PR, choose autoclose targets carefully:
| Reference style | Effect on merge | Use for |
|---|---|---|
Fixes #N / Closes #N / Resolves #N | GitHub auto-closes #N when PR merges | Sub-issues fully resolved by this PR |
Related to #N / Refs #N | Issue stays open after merge | Umbrella issues with more phases pending |
A bundled PR commonly references both โ Fixes #348 (sub-issue resolved) + Related to #298 (umbrella stays open for follow-up phases).
Workflow flowchart โ
flowchart TD
INIT[New initiative] --> ISSUE[Open umbrella issue<br/>Lock in design + decisions]
ISSUE --> SCOPE{Scope<br/>analysis}
SCOPE -->|truly independent work<br/>different reviewers/timelines| MULTI[Multiple PRs<br/>rare]
SCOPE -->|related theme<br/>default| ONE[Single PR strategy]
ONE --> KICKOFF[Ask: main checkout<br/>or worktree?<br/>Confirm fresh branch from dev]
KICKOFF --> SETUP{User chose<br/>worktree?}
SETUP -->|No: main checkout| BRANCH[Create feat/N-topic branch<br/>in main checkout]
SETUP -->|Yes: worktree| WTSETUP[Create worktree<br/>symlink .env.local<br/>npm ci]
BRANCH --> PARALLEL{Need parallel<br/>development?}
WTSETUP --> PARALLEL
PARALLEL -->|No: single contributor| WORK[Work + commit]
PARALLEL -->|Yes: multiple contributors<br/>or parallel concerns| WT2[Multiple worktrees<br/>+ symlink .env.local]
WT2 --> SUB{Sub-branches<br/>or shared parent?}
SUB -->|Shared parent<br/>simpler, must serialize| WORK
SUB -->|Sub-branches<br/>true parallelism| MERGE[Merge sub-branches<br/>into parent locally<br/>before pushing]
MERGE --> WORK
WORK --> VALIDATE[npm run validate]
VALIDATE --> PUSH[Push to remote<br/>parent branch only]
PUSH --> READY{Ready for<br/>review?}
READY -->|No, more work| WORK
READY -->|Yes| PR[gh pr create<br/>Fixes/Related to issues]
PR --> CI[CI engages<br/>each push reruns]
CI --> REVIEW{Review feedback?}
REVIEW -->|Changes needed| FIX[Commit fixes<br/>BATCH them]
FIX --> CI
REVIEW -->|Approved| MERGEPR[Merge to dev<br/>squash for feature โ dev]
MERGEPR --> CLEANUP[git worktree remove<br/>delete remote branch]
CLEANUP --> CLOSE{Umbrella issue<br/>more phases?}
CLOSE -->|Yes| INIT
CLOSE -->|No| DONE[Close umbrella]
style ISSUE fill:#e0e7ff,stroke:#4338ca
style ONE fill:#dcfce7,stroke:#16a34a
style MULTI fill:#fee2e2,stroke:#dc2626
style PUSH fill:#dcfce7,stroke:#16a34a
style PR fill:#fef3c7,stroke:#d97706
style CI fill:#fee2e2,stroke:#dc2626
style CLEANUP fill:#e0e7ff,stroke:#4338caColor legend:
- ๐ข Green: free actions (no CI cost)
- ๐ก Amber: cost-incurring transition (PR opens, CI engages)
- ๐ด Red: high-cost or rare scenarios
- ๐ฃ Indigo: process discipline (issue tracking, cleanup)
Worktree usage โ
Git worktrees let you check out multiple branches into separate directories simultaneously, sharing a single .git folder. Useful when you need local isolation; not a default.
If an AI agent or teammate is starting the work, they should ask before creating one. The repo default recommendation is still the main checkout unless the user explicitly chooses worktree isolation.
When to use โ
- Multiple parallel concerns (e.g. reviewing a teammate's PR while own code is mid-edit)
- Multi-contributor parallel work converging to one PR
- Running two different branches' dev servers concurrently
When to skip โ
- Sequential PR work in a single session โ just
git checkout -bin the main checkout - Simple one-PR branch work โ overhead doesn't pay off
Required setup โ
When creating a worktree in this repo:
# 1. Create the worktree
git worktree add /home/<user>/repos/lantern_app/.claude/worktrees/<name> <branch>
# 2. Symlink .env.local (gitignored, but required by Vite + API services)
ln -s /home/<user>/repos/lantern_app/.env.local \
/home/<user>/repos/lantern_app/.claude/worktrees/<name>/.env.local
# 3. Install dependencies (worktrees do NOT share node_modules)
cd /home/<user>/repos/lantern_app/.claude/worktrees/<name>
npm ci # ~1-2 min with hot npm cacheWithout the symlink, the API services and Vite fail to find environment variables. Without npm ci, lint and tests can't run.
Cleanup โ
After the work merges, remove the worktree:
git worktree list # show all worktrees
git worktree remove --force <path> # delete oneEach worktree is ~500 MB with node_modules โ disk bloats fast without cleanup.
Parallel work via sub-branches โ
When two contributors work on the same initiative concurrently and conflict-prone files are involved, use sub-branches that merge into the parent feature branch locally before any push.
feat/298-release-pipeline (parent โ the eventual PR branch)
โโโ feat/298-release-pipeline--phase-1.5 (sub-branch, contributor A)
โโโ feat/298-release-pipeline--phase-2 (sub-branch, contributor B)
โโโ feat/298-release-pipeline--phase-3 (sub-branch, contributor C)Workflow:
- Each contributor works in their own worktree on their sub-branch
- When a sub-branch is ready, merge it into the parent branch locally (in any worktree)
- Push only the parent branch to origin
- Sub-branches stay local and can be deleted after merging
Avoid:
- Pushing sub-branches to origin (creates noise, defeats the "single PR" goal)
- Force-pushing the parent branch when others are working from it (nukes their work)
- Two contributors pushing to the parent branch simultaneously (race condition guaranteed)
If you don't need true parallelism, serialize: one contributor active on the parent branch at a time, no sub-branches needed.
Push timing & CI cost โ
GitHub Actions billing is workflow-minutes-based. Three states matter:
| State | What fires | Cost |
|---|---|---|
Push to main / dev | ci.yml, discord-notify.yml, deploy-*.yml | High โ the full pipeline |
| Push to feature branch with open PR | ci.yml (via pull_request: synchronize), deploy-preview.yml if labeled | Medium โ repeats per push |
| Push to feature branch with no PR yet | issue-automation.yml once on branch creation (~15s) | Negligible |
Implication: while a feature branch has no PR open, push iteratively without worrying about CI cost. The cost meter starts the moment gh pr create runs. Batch fixes once a PR is open to avoid burning minutes on each git push.
workflow_dispatch is available on deploy-preview.yml for one-off testing without opening a PR โ useful for validating the deploy pipeline against a feature branch.
Validation before push โ
Per AGENTS.md Non-Negotiable Rule 3: run npm run validate before opening a PR (not per-commit). Fix every failure locally first; pushing known-broken work wastes CI minutes. This applies to feature branches too, not just merges.
In a worktree, run npm ci first. Environmental lint failures (e.g. eslint-plugin-react resolution conflicts when both worktree and primary tree have node_modules) are real but verifiable โ CI in a clean container is the source of truth.
Scoped validation for targeted iterations:
npm run validate -- --scope lint,format # skip tests + audit
npm run validate -- --workspace apps/web # only one workspaceQuick reference โ
# Kickoff
# Ask: main checkout or worktree?
# New initiative
gh issue create --title "..." --body "..." --milestone Prototype
git checkout -b feat/<N>-<topic> dev
# Optional worktree (when isolation matters)
git worktree add .claude/worktrees/<name> feat/<N>-<topic>
ln -s "$PWD/.env.local" .claude/worktrees/<name>/.env.local
cd .claude/worktrees/<name> && npm ci
# Iterate (push freely, no PR yet)
git add ... && git commit -m "..."
git push -u origin feat/<N>-<topic>
# Open PR when ready
gh pr create --base dev --title "..." \
--body "Fixes #<sub-issue>. Related to #<umbrella>."
# After merge
git worktree remove --force .claude/worktrees/<name>
git push origin --delete feat/<N>-<topic>See also โ
- CHANGELOG_WORKFLOW.md โ what to record per merge
- AUTOMATED_COMMITS.md โ commit-message tag conventions
- DEVELOPER_ONBOARDING.md โ getting started
docs/CONTRIBUTING.mdโ top-level contributor guidelines