Skip to content

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 dev for 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>
PatternUse
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-pipeline
  • fix/348-skip-logic
  • fix/337-followup-assistant-deploy
  • chore/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 styleEffect on mergeUse for
Fixes #N / Closes #N / Resolves #NGitHub auto-closes #N when PR mergesSub-issues fully resolved by this PR
Related to #N / Refs #NIssue stays open after mergeUmbrella 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 โ€‹

mermaid
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:#4338ca

Color 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 -b in the main checkout
  • Simple one-PR branch work โ€” overhead doesn't pay off

Required setup โ€‹

When creating a worktree in this repo:

bash
# 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 cache

Without 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:

bash
git worktree list                                          # show all worktrees
git worktree remove --force <path>                         # delete one

Each 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:

  1. Each contributor works in their own worktree on their sub-branch
  2. When a sub-branch is ready, merge it into the parent branch locally (in any worktree)
  3. Push only the parent branch to origin
  4. 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:

StateWhat firesCost
Push to main / devci.yml, discord-notify.yml, deploy-*.ymlHigh โ€” the full pipeline
Push to feature branch with open PRci.yml (via pull_request: synchronize), deploy-preview.yml if labeledMedium โ€” repeats per push
Push to feature branch with no PR yetissue-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:

bash
npm run validate -- --scope lint,format        # skip tests + audit
npm run validate -- --workspace apps/web       # only one workspace

Quick reference โ€‹

bash
# 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 โ€‹

Built with VitePress