Avoiding branch sprawl and maintaining hygiene
Avoiding branch sprawl and maintaining hygiene
TL;DR:
- Set time limits: branches beyond 30 days should have someone actively justifying their existence.
- Assign a single person (tech lead or CMS architect) to own branch governance — shared responsibility means no responsibility.
- Delete branches immediately after a successful merge and post-merge verification.
A branch that exists for two weeks and gets merged is a tool. A branch that exists for six months and no one remembers why is a liability. Branch sprawl - the accumulation of stale, abandoned, or forgotten branches - is one of the most common operational problems in teams that adopt branching without establishing lifecycle discipline. Every unmerged branch represents divergence from main, and divergence that grows unchecked eventually makes merging so painful that teams avoid it entirely. At that point, the branches are not enabling parallel development. They are preventing integration.
Why long-lived branches become liabilities
The core issue is divergence. When you create a branch, it captures a snapshot of your content model at that moment. From that point, the branch and main evolve independently. Every change made to main after the branch was created is a change the branch does not have. Every change made on the branch is a change main does not have. The gap between the two widens every day.
After one week, the gap is usually small - a few field additions or modifications on main. After one month, the gap may include new content types, modified global fields, and structural changes that affect multiple content types. After three months, the branch's view of the content model may be so different from main's current state that merging requires essentially rebuilding the branch's changes from scratch.
Long-lived branches also create operational confusion:
- New team members encounter branches in the branch list and do not know if they are active, abandoned, or awaiting merge. They may accidentally work on a stale branch.
- Branch list clutter makes it harder to find active branches in the Contentstack UI. When you have 15 branches and only 3 are active, finding the right one takes unnecessary effort.
- Resource allocation is obscured. Branches that represent work-in-progress create a false sense that projects are active when they may have been abandoned or deprioritized.
- Merge conflicts compound. The longer two branches diverge, the more conflicts accumulate. A branch that could have been cleanly merged after two weeks may require significant conflict resolution after two months.
Branch lifecycle management
Every branch should follow a defined lifecycle: create, develop, test, merge, delete. Each phase has an expected duration, and exceeding that duration should trigger a review.
Phase 1: Create
A branch is created in response to a specific need - a new content type, a schema restructuring, a migration. The creation should be accompanied by an entry in the branch registry (as described in the parallel development lesson) that records the branch name, owner, purpose, creation date, and expected merge date.
Phase 2: Develop
The development phase is where schema changes are made on the branch. This should be time-boxed. Most content model changes - even complex ones - can be completed within 2-4 weeks. If development is taking longer, the scope may be too large and should be broken into smaller, independently mergeable pieces.
Phase 3: Test
The testing phase validates the branch's changes against the frontend. Build your application against the branch's API, verify rendering, and confirm that the delivery API returns the expected response shapes. Testing should take days, not weeks.
Phase 4: Merge
Execute the merge following the compare-and-merge process. Coordinate with the frontend deployment and communicate changes to the content team.
Phase 5: Delete
After a successful merge and post-merge verification, delete the branch. There is no reason to keep a merged branch. It has served its purpose. Its changes are now on main. Keeping it around adds clutter and creates the false impression that it is still relevant.
Setting time limits
Time limits are the simplest and most effective governance mechanism for branches. They do not require sophisticated tooling or complex processes - just a policy that the team agrees to follow.
A practical time limit policy:
| Branch age | Action required |
|---|---|
| 0-14 days | Normal development. No action needed. |
| 14-30 days | Owner should provide a status update. Is the branch on track? |
| 30-60 days | Branch requires a review. Is it still needed? Can the scope be reduced? Should it be rebased against current main? |
| 60-90 days | Branch is flagged as at-risk. The team lead reviews whether to continue, reduce scope, or abandon. |
| 90+ days | Branch is presumed stale unless the owner can justify its continued existence. Default action is to delete it. |
These thresholds are guidelines, not rigid rules. A 3-month branch for a major platform migration might be justified. A 3-month branch for “adding a video content type” is not. The point is that every branch beyond 30 days should have someone actively justifying its existence.
Common pitfall:
Deleting a branch is irreversible. If the branch contained unmerged schema changes you still need, they are gone. Always verify a successful merge before deleting.
The delete operation
Deleting a branch removes it and its content type modifications permanently.
Through the UI: navigate to Settings > Branches, select the branch, and click “Delete.” Contentstack asks for confirmation.
Through the API: use the branch delete endpoint.
// Deleting a branch via the Management API
const response = await fetch(
"https://api.contentstack.io/v3/stacks/branches/feature-product-catalog",
{
method: "DELETE",
headers: {
api_key: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY!,
authorization: process.env.NEXT_PUBLIC_CONTENTSTACK_MANAGEMENT_TOKEN!,
},
}
);
if (response.ok) {
console.log("Branch deleted successfully");
} else {
const error = await response.json();
console.error("Delete failed:", error.error_message);
}Before deleting, verify:
- The branch has been successfully merged (if it contains changes you want to keep).
- No frontend deployments are still pointing at the branch. A QA environment configured to build against a deleted branch will fail on its next build.
- No team members are actively working on the branch. Communicate the deletion before executing it.
The main branch cannot be deleted. It is the permanent production branch of the stack.
Who should manage branches
Branch governance works best when a single person or role owns the process. In most organizations, this is the technical lead, CMS architect, or lead developer - someone with visibility into all active development workstreams and the authority to enforce lifecycle policies.
The branch owner's responsibilities:
- Create branches: approve branch creation requests and ensure they have a clear purpose, scope, and expected merge date.
- Monitor branch age: regularly review the branch list and flag branches that are approaching or exceeding time limits.
- Coordinate merges: sequence merges to minimize conflicts, as described in the parallel development lesson.
- Enforce deletion: ensure branches are deleted after merging. Chase down owners of stale branches.
- Maintain the branch registry: keep the shared document up to date with current branch status.
Without a designated owner, branch management becomes a shared responsibility that no one actually takes. Branches accumulate, merges are postponed, and the branch list grows until someone inherits the mess.
Monitoring branch drift
For long-lived branches that are justified (major migrations, multi-month redesigns), periodically compare the branch to main to assess how much they have diverged. This is not the same as a pre-merge compare - it is a health check.
// Monitor branch drift by counting content type differences
async function assessBranchDrift(branchUid: string) {
const fetchTypes = async (branch: string) => {
const response = await fetch(
"https://api.contentstack.io/v3/content_types?include_count=true",
{
headers: {
api_key: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY!,
authorization: process.env.NEXT_PUBLIC_CONTENTSTACK_MANAGEMENT_TOKEN!,
branch,
},
}
);
return response.json();
};
const [branchData, mainData] = await Promise.all([
fetchTypes(branchUid),
fetchTypes("main"),
]);
const branchUids = new Set(
branchData.content_types.map((ct: any) => ct.uid)
);
const mainUids = new Set(
mainData.content_types.map((ct: any) => ct.uid)
);
const onlyOnBranch = [...branchUids].filter((uid) => !mainUids.has(uid));
const onlyOnMain = [...mainUids].filter((uid) => !branchUids.has(uid));
console.log(`Branch: ${branchUid}`);
console.log(`Content types only on branch: ${onlyOnBranch.length}`);
console.log(`Content types only on main: ${onlyOnMain.length}`);
console.log(`Total divergence indicators: ${onlyOnBranch.length + onlyOnMain.length}`);
if (onlyOnMain.length > 0) {
console.warn(
`Warning: main has ${onlyOnMain.length} content types that this branch does not. ` +
`Consider updating the branch to incorporate these changes.`
);
}
}
assessBranchDrift("redesign-homepage-modular");If the drift assessment reveals significant divergence, the branch owner has two options:
- Rebase the branch: create a new branch from current main and re-apply the branch's changes. This resets the divergence clock but requires manual effort.
- Accelerate the merge: prioritize completing the branch's work and merge sooner to limit further divergence.
Neither option is free. Both cost time. The purpose of monitoring is to make the cost visible so the team can make an informed decision rather than discovering the divergence at merge time when the pressure is highest.
The cost of abandoned branches
Abandoned branches - branches that no one is working on, no one plans to merge, and no one has deleted - impose real costs:
Confusion for new team members. A new developer joins the team, opens the branch list, and sees 12 branches. Which ones are active? Which are stale? Without a branch registry or naming convention that includes status, they have to ask. Or worse, they do not ask and start working on a stale branch.
Wasted Contentstack resources. Branches consume storage for their content type definitions and entries. While the cost per branch is modest, abandoned branches across multiple stacks add up - particularly for agencies managing many client stacks.
False sense of progress. A branch named feature/personalization-engine creates the impression that the personalization engine is in development. If the branch was abandoned three months ago, this impression is misleading. Stakeholders who see the branch in a status report may believe work is underway when it is not.
Increased merge complexity for active branches. When multiple branches exist, merge sequencing must account for all of them. Abandoned branches that no one intends to merge still occupy mental space in the planning process until someone confirms they are no longer relevant.
Governance without over-process
Branch governance does not require heavy process. It requires visibility and accountability. A simple governance framework:
The branch registry
A shared spreadsheet or document that tracks every active branch:
| Branch name | Owner | Purpose | Created | Expected merge | Last reviewed |
|---|---|---|---|---|---|
| feature/product-catalog | Sarah Chen | New product content types | 2026-01-15 | 2026-02-10 | 2026-01-29 |
| redesign/homepage | Mike Johnson | Homepage modular blocks | 2026-01-10 | 2026-02-20 | 2026-02-01 |
| migration/article-v2 | Lisa Park | Article body restructuring | 2026-01-20 | 2026-02-12 | 2026-01-30 |
The “Last reviewed” column is key. It confirms that someone has recently verified the branch is still active and on track. A branch with a “Last reviewed” date from three months ago is a candidate for deletion.
Weekly branch review
During the team's regular standup or weekly planning meeting, spend two minutes reviewing the branch registry:
- Are any branches past their expected merge date?
- Are any branches approaching the 30-day threshold?
- Have any branches been created since the last review? If so, do they have registry entries?
Automated branch monitoring
For teams managing multiple stacks, manual tracking does not scale. Use the Branches API to automate branch monitoring:
// List all branches and flag stale ones
async function auditBranches() {
const response = await fetch(
"https://api.contentstack.io/v3/stacks/branches",
{
headers: {
api_key: process.env.NEXT_PUBLIC_CONTENTSTACK_API_KEY!,
authorization: process.env.NEXT_PUBLIC_CONTENTSTACK_MANAGEMENT_TOKEN!,
},
}
);
const data = await response.json();
const now = new Date();
data.branches.forEach((branch: any) => {
if (branch.uid === "main") return; // Skip main
const created = new Date(branch.created_at);
const ageInDays = Math.floor(
(now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24)
);
let status = "OK";
if (ageInDays > 90) status = "STALE - consider deleting";
else if (ageInDays > 60) status = "AT RISK - review needed";
else if (ageInDays > 30) status = "AGING - check status";
console.log(
`${branch.uid} | Age: ${ageInDays} days | Created: ${branch.created_at} | ${status}`
);
});
}
auditBranches();This script can run as a scheduled job (via a cron job, a GitHub Actions schedule, or an Automation Hub flow if you use a webhook connector) that posts results to a Slack channel. When a branch crosses the 30-day threshold, the team is notified automatically.
Example: quarterly cleanup at a multi-stack agency
An agency manages 10 Contentstack stacks for different clients. Each stack has its own content model, development team, and release schedule. Over a quarter, branches accumulate across all stacks as different client projects create and sometimes forget to clean up branches.
The agency's CMS architect runs a quarterly branch audit:
- Inventory: script queries the Branches API for all 10 stacks, listing every non-main branch with its age and creation date.
- Classification: branches are classified as active (owner confirms ongoing work), stale (no activity in 30+ days), or abandoned (no owner responds).
- Review: stale branches are compared to main to assess divergence. If the divergence is small, the branch might be worth merging. If the divergence is large and the project is deprioritized, the branch should be deleted.
- Action: abandoned branches are deleted after confirmation from the client stakeholder. Stale branches are either merged (if the work is complete) or deleted (if the work is no longer needed).
- Documentation: the branch registry is updated to reflect the current state of each stack.
In a recent quarterly audit, the architect found:
- Stack A (retail client): 4 branches, 2 abandoned (both older than 120 days). Deleted both.
- Stack B (media client): 6 branches, 3 stale, 1 abandoned. Merged 2 stale branches that contained completed work. Deleted the abandoned branch and the remaining stale branch after confirming the work was deprioritized.
- Stack C (healthcare client): 3 branches, all active. No action needed.
- Remaining stacks: 2 abandoned branches total across 7 stacks. Deleted both.
The cleanup removed 8 abandoned or stale branches, reducing confusion for developers working across multiple client stacks and eliminating the risk of someone accidentally building against a stale branch.
The quarterly cadence works for the agency model. Teams that manage a single stack with active development may need a monthly or even weekly review cadence.
Building a branch hygiene culture
Tools and processes help, but branch hygiene ultimately depends on the team treating branches as temporary, purpose-specific tools rather than permanent features of the stack.
Name branches with intent. A branch named feature/product-catalog-q1-2026 communicates its purpose and expected lifespan. A branch named test communicates nothing and will likely be abandoned.
Set a merge date when creating the branch. Before creating a branch, state when you expect to merge it. This sets an expectation that the branch has a finite lifespan and creates accountability for meeting that deadline.
Delete branches immediately after merging. Do not wait for the “next cleanup.” Delete the branch as part of the merge process. Merge, verify, delete - in the same work session.
Treat stale branches as technical debt. An unmerged branch older than 30 days is technical debt. It represents work that is either incomplete, deprioritized, or abandoned. Like all technical debt, it accrues interest - the longer it sits, the more expensive it is to resolve.
Common mistakes
Mistake 1: Keeping branches “just in case”
After merging a branch, some teams keep the branch around as a backup or reference. This is unnecessary. The merge applied the branch's changes to main. The branch no longer contains unique information. If you need a backup of main's state before the merge, create a backup branch from main before merging (not after). Delete the source branch once the merge is verified.
Mistake 2: No single person responsible for branch governance
When branch management is “everyone's responsibility,” it is no one's responsibility. Designate a specific person to own branch hygiene. This person reviews the branch list regularly, follows up with branch owners, and enforces deletion of stale branches.
Mistake 3: Creating branches for exploratory work with no merge plan
Experimental branches - “let me try restructuring this content type” - are fine if they have a short time limit (1-2 weeks). They become problems when the experiment does not produce results but the branch is never deleted. If an experimental branch does not lead to a merge-worthy outcome within its time limit, delete it and document what you learned.