Superseded on 2026-04-05 by
2026-04-03-1-execution-lanes-own-worktrees-and-mission-branches.md.target_branchremains the feature's merge destination, but the runtime no longer models implementation/status ancestry as "target branch plus one WP branch". Mission and lane branches now derive from the target branch instead.
Context and Problem Statement
ADR-12 established a two-branch strategy: main (1.x product) and 2.x (SaaS product). However, spec-kitty's status commit logic was hard-coded to commit to main, causing race conditions and branch divergence for 2.x features.
Manifestation (Feature 025 on 2.x):
t=0: Feature 025 created, targets 2.x branch
t=1: WP01 implement creates branch from main (WRONG - should be 2.x)
t=2: Status commits go to main (WRONG - should be 2.x)
t=3: Implementation commits go to WP01 branch
t=4: 2.x advances with new commits (diverges from WP01 base)
t=5: Review fails: git merge-base --is-ancestor 2.x HEAD → FAIL
t=6: Manual rebase onto 2.x required
t=7: More status commits to main (race repeats)
Problem: How do we route status commits (move-task, mark-status) to the correct target branch (main for 1.x features, 2.x for SaaS features) instead of hard-coding to main?
Decision Drivers
- Dual-branch development - Must support parallel 1.x and 2.x development (ADR-12)
- Branch isolation - Main and 2.x must not contaminate each other
- Race condition prevention - Status commits must share ancestry with implementation commits
- Backward compatibility - Existing features (pre-0.13.8) must continue working
- Explicit configuration - Target branch should be visible in metadata (no magic)
- Hotfix urgency - Need minimal changes to ship quickly (defer full git state machine to 2.0)
Considered Options
- Option 1: Metadata-driven routing with
target_branchfield in meta.json - Option 2: Git branch detection (infer target from current branch)
- Option 3: Environment variable (SPEC_KITTY_TARGET_BRANCH)
- Option 4: Feature number convention (025+ → 2.x, 001-024 → main)
Decision Outcome
Chosen option: "Option 1: Metadata-driven routing with target_branch field", because:
- Explicit and visible in meta.json (debugging-friendly)
- Deterministic (same feature → same target)
- Flexible (can change per feature, not per number)
- Self-documenting (grep for "target_branch": "2.x")
- Supports future multi-branch scenarios (3.x, dev, staging)
Consequences
Positive
- Branch isolation maintained - Main never sees 2.x status commits
- Race condition eliminated - Status and implementation share target branch
- Explicit configuration - Target visible in
cat meta.json - Backward compatible - Legacy features default to "main"
- Debuggable - Can grep for features by target:
grep -r '"target_branch": "2.x"' - Flexible - Can route any feature to any branch (not just 1.x/2.x)
Negative
- Migration required - Must add target_branch to existing features (m_0_13_8_target_branch)
- Manual specification - Users must set target_branch during feature creation
- Potential confusion - Users might forget to set for 2.x features
- Not fully automated - Could infer from branch, but chose explicit over implicit
Neutral
- Default to "main" - Safe fallback for legacy features
- Detection function -
get_feature_target_branch(repo_root, feature_slug)→ string - Routing pattern - Checkout target, commit, restore original branch
Confirmation
We validated this decision by:
- ✅ 12 integration tests for dual-branch routing (all passing)
- ✅ Feature 025 successfully developing on 2.x
- ✅ Main branch completely isolated (zero 2.x commits)
- ✅ git merge-base --is-ancestor checks passing
- ✅ Migration adds target_branch to all existing features
Pros and Cons of the Options
Option 1: Metadata-driven routing (CHOSEN)
Explicit target_branch field in meta.json, read by status commands.
Pros:
- Explicit and visible (cat meta.json shows config)
- Deterministic (not environment-dependent)
- Self-documenting (grep finds features by target)
- Flexible (supports any branch name)
- Testable (can mock meta.json)
Cons:
- Requires migration (add field to existing features)
- Manual specification during feature creation
- Could forget to set for new features
- Adds to meta.json schema
Option 2: Git branch detection
Infer target from current git branch (if on 2.x, route to 2.x).
Pros:
- No metadata changes required
- Automatic (no user input needed)
- Follows current context
Cons:
- Implicit and invisible (debugging hard)
- Environment-dependent (CWD affects behavior)
- Ambiguous (what if on feature branch?)
- Not self-documenting
Option 3: Environment variable
Set SPEC_KITTY_TARGET_BRANCH=2.x in environment.
Pros:
- Override capability
- No file changes
- Easy to test
Cons:
- Not persisted (lost on shell restart)
- Not visible in feature metadata
- Easy to forget or misconfigure
- Environment pollution
Option 4: Feature number convention
Features 025+ automatically route to 2.x.
Pros:
- No configuration needed
- Clear convention
- Automatic
Cons:
- Magic number threshold (not explicit)
- Inflexible (what about 3.x?)
- Not self-documenting
- Assumes numeric ordering = product split
More Information
Implementation:
src/specify_cli/core/feature_detection.py::get_feature_target_branch()src/specify_cli/cli/commands/agent/tasks.py(lines 669-728: move_task routing)src/specify_cli/cli/commands/agent/tasks.py(lines 839-887: mark_status routing)src/specify_cli/upgrade/migrations/m_0_13_8_target_branch.py
Tests:
tests/integration/test_dual_branch_status_routing.py(9 tests)tests/integration/test_feature_025_workflow.py(3 tests)tests/specify_cli/core/test_feature_detection.py::test_get_feature_target_branch_*(6 tests)tests/specify_cli/upgrade/test_m_0_13_8_target_branch.py(11 tests)
Related ADRs:
- ADR-12: Two-Branch Strategy for SaaS Transformation (establishes need for dual-branch support)
Version: 0.13.8 (hotfix enabling 2.x development)
Supersedes: None (new capability)
Superseded by: Will be replaced by full Git State Machine in 2.0 (Phase 2 plan deferred to 2.x branch)