Skip to main content
Technology & EngineeringGit Workflow117 lines

Trunk Based

Trunk-based development for continuous integration with short-lived branches

Quick Summary11 lines
You are an expert in trunk-based development for continuous integration and rapid delivery.

## Key Points

- Integrate at least once per day; branches older than two days signal a process problem
- Use feature flags to separate deployment from release, allowing incomplete features to ship safely
- Require linear history with fast-forward merges or squash merges to keep the trunk clean
- Letting branches live too long, which reintroduces the merge-conflict pain trunk-based development is designed to avoid
- Relying on feature branches for isolation instead of feature flags, which delays integration and hides conflicts
skilldb get git-workflow-skills/Trunk BasedFull skill: 117 lines
Paste into your CLAUDE.md or agent config

Trunk-Based Development — Git Workflows

You are an expert in trunk-based development for continuous integration and rapid delivery.

Overview

Trunk-based development is a branching strategy where developers integrate small, frequent changes directly into a single shared branch (the trunk, typically main). Short-lived feature branches may exist but are merged within one to two days. This model minimizes merge conflicts and supports continuous delivery.

Core Concepts

The trunk: A single long-lived branch (main) where all developers integrate their work.

Short-lived branches: Feature branches that live for hours to a maximum of two days before merging.

Feature flags: Decouple deployment from release by hiding incomplete features behind runtime toggles.

# Start work on a short-lived branch
git checkout -b feat/add-search main
# ... make small, focused changes ...
git push -u origin feat/add-search

# Keep the branch fresh (daily or more)
git fetch origin
git rebase origin/main

# Merge quickly (same day or next day)
git checkout main
git merge --ff-only feat/add-search
git push origin main
git branch -d feat/add-search

Feature flag pattern (application code):

// Runtime toggle — ship code that isn't user-visible yet
if (featureFlags.isEnabled('new-search')) {
  renderNewSearch();
} else {
  renderLegacySearch();
}

Implementation Patterns

Stacked diffs / dependent PRs:

# First PR
git checkout -b feat/api-layer main
# ... implement API ...
git push -u origin feat/api-layer

# Second PR builds on the first
git checkout -b feat/ui-layer feat/api-layer
# ... implement UI ...
git push -u origin feat/ui-layer

# After first PR merges, rebase the second onto main
git checkout feat/ui-layer
git rebase origin/main
git push --force-with-lease

Enforcing trunk health with CI:

# GitHub Actions — require passing checks before merge
on:
  pull_request:
    branches: [main]
jobs:
  gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test && npm run lint

Core Philosophy

Trunk-based development is built on a single insight: integration pain grows exponentially with branch lifetime. A branch that lives for one day has a small diff and a trivial merge. A branch that lives for two weeks has accumulated conflicts with every other branch that merged during that period, and the merge becomes a risky, time-consuming event. By keeping branches short-lived (hours to one or two days) and integrating into the trunk continuously, trunk-based development converts one large, scary integration into many small, routine ones. The total work is less because conflicts are caught and resolved when they are small.

Feature flags are the enabling technology that makes trunk-based development practical. Without feature flags, the only way to hide incomplete work from users is to keep it on a branch. With feature flags, incomplete code can be merged to the trunk, deployed to production, and remain invisible to users until the flag is enabled. This separates two concerns that traditional branching conflates: deployment (putting code on servers) and release (exposing features to users). An organization that deploys every commit but releases features deliberately through flags has the best of both worlds: continuous integration with controlled rollout.

Trunk health is the non-negotiable invariant. If the trunk is broken, every developer on the team is blocked. This means the bar for merging to trunk must be high and automated: a comprehensive CI pipeline that runs fast enough to provide feedback before the developer moves on to the next task. Branch protection rules, required status checks, and fast CI are not optional guardrails — they are the immune system that keeps the trunk in a permanently releasable state. A team that adopts trunk-based development without investing in CI speed and reliability will quickly find that a broken trunk is worse than long-lived branches.

Anti-Patterns

  • Long-lived feature branches. Branches that live for a week or more accumulate merge conflicts, diverge significantly from the trunk, and reintroduce the integration pain that trunk-based development is designed to eliminate. If a feature cannot be completed in one to two days, break it into smaller increments and use feature flags to hide incomplete work.

  • Using branches instead of feature flags for isolation. Keeping code on a branch to "hide it from users" delays integration and creates merge conflicts. Deploy the code to production behind a feature flag so it is integrated continuously but invisible to users until it is ready.

  • No CI gate on the trunk. Allowing direct pushes to main without passing CI means a single broken commit blocks every developer. Require passing status checks (tests, lint, build) before any merge to the trunk, and invest in CI speed so the gate is fast enough to not be a bottleneck.

  • Rebasing instead of fast-forward merging without intent. Frequent rebasing rewrites history, which can confuse tooling and other developers. Use fast-forward merges (--ff-only) or squash merges for a clean linear history, and rebase only when the branch needs to incorporate upstream changes before merging.

  • Feature flags that never get cleaned up. Flags introduced to hide incomplete work that are never removed after the feature is fully released become permanent branching logic in the codebase. Establish a flag lifecycle: create, activate, fully roll out, remove the conditional code, delete the flag.

Best Practices

  • Integrate at least once per day; branches older than two days signal a process problem
  • Use feature flags to separate deployment from release, allowing incomplete features to ship safely
  • Require linear history with fast-forward merges or squash merges to keep the trunk clean

Common Pitfalls

  • Letting branches live too long, which reintroduces the merge-conflict pain trunk-based development is designed to avoid
  • Relying on feature branches for isolation instead of feature flags, which delays integration and hides conflicts

Install this skill directly: skilldb add git-workflow-skills

Get CLI access →