Skip to content

fix(cleanup): read worktree.baseBranch config before git default-branch detection#1925

Merged
Wirasm merged 3 commits into
devfrom
archon/task-fix-issue-1419
Jun 8, 2026
Merged

fix(cleanup): read worktree.baseBranch config before git default-branch detection#1925
Wirasm merged 3 commits into
devfrom
archon/task-fix-issue-1419

Conversation

@Wirasm

@Wirasm Wirasm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Problem: The cleanup service called getDefaultBranch() unconditionally at 3 call sites, causing repeated env_cleanup_error logs on every startup for repos whose default branch is not main and where origin/HEAD is not configured. The error message told users to set worktree.baseBranch in .archon/config.yaml, but that config value was never read by the cleanup service — making the suggestion useless.
  • Why it matters: Every tracked environment on a master-branch repo triggered a logged error per startup with no viable workaround. Prior fix attempts (fix(core): respect worktree.baseBranch in cleanup service default branch resolution #1423, fix(core): respect worktree.baseBranch in cleanup service #1424) were closed without landing.
  • What changed: Added a private resolveBaseBranch helper in cleanup-service.ts that reads worktree.baseBranch from .archon/config.yaml first, falling back to getDefaultBranch() only when nothing is configured. Replaced all 3 getDefaultBranch call sites with resolveBaseBranch. Added 4 regression tests.
  • What did not change: getDefaultBranch itself is unchanged; config-loader.ts is unchanged; no interface or DB schema changes; the codebase.default_branch DB-column fallback (mentioned as "and/or" in the issue) was intentionally omitted to keep scope minimal.

UX Journey

Before

Startup
  ┌─ for each tracked environment
  │    cleanupService.runScheduledCleanup()
  │      getDefaultBranch(mainRepoPath)   ← throws if origin/HEAD unset + no origin/main
  │        ERROR: env_cleanup_error logged  ← repeats every startup, per env
  │        User sees: "Set worktree.baseBranch in .archon/config.yaml"
  │          ↳ User sets config…
  │            ↳ Error still fires (config never read)  ← dead end
  └─ repeat every startup

After

Startup
  ┌─ for each tracked environment
  │    cleanupService.runScheduledCleanup()
  │      resolveBaseBranch(mainRepoPath, cwd)
  │        1. loadRepoConfig(cwd)
  │           worktree.baseBranch set?  →  return configured branch  ← no error
  │           not set?
  │        2. getDefaultBranch(mainRepoPath)
  │           origin/HEAD exists?   →  return detected branch
  │           origin/main exists?   →  return 'main'
  │           neither?              →  throw (legitimate detection failure)
  └─ repeat every startup

Architecture Diagram

Before

cleanup-service.ts
  runScheduledCleanup()       ──calls──▶  @archon/git:getDefaultBranch()
  getWorktreeStatusBreakdown() ──calls──▶  @archon/git:getDefaultBranch()
  cleanupMergedWorktrees()    ──calls──▶  @archon/git:getDefaultBranch()

config-loader.ts (loadRepoConfig)  ◀── NOT imported in cleanup-service.ts

After

cleanup-service.ts
  resolveBaseBranch()  [+]    ──reads──▶  config-loader.ts:loadRepoConfig()  [+new connection]
                              ──calls──▶  @archon/git:getDefaultBranch()  (fallback only)

  runScheduledCleanup()       ──calls──▶  resolveBaseBranch()  [~ was getDefaultBranch]
  getWorktreeStatusBreakdown() ──calls──▶  resolveBaseBranch()  [~ was getDefaultBranch]
  cleanupMergedWorktrees()    ──calls──▶  resolveBaseBranch()  [~ was getDefaultBranch]

Connection inventory:

From To Status Notes
cleanup-service.ts @archon/git:getDefaultBranch modified Now indirect via resolveBaseBranch; not called when config provides branch
cleanup-service.ts config-loader.ts:loadRepoConfig new Added to consult worktree.baseBranch first
resolveBaseBranch @archon/git:getDefaultBranch new Fallback path when no config value set
resolveBaseBranch config-loader.ts:loadRepoConfig new Primary resolution path

Label Snapshot

  • Risk: risk: low
  • Size: size: S
  • Scope: core
  • Module: core:cleanup-service

Change Metadata

  • Change type: bug
  • Primary scope: core

Linked Issue

Validation Evidence (required)

bun run validate

All checks passed on the first run:

Check Result
Bundled defaults ✅ 36 commands, 20 workflows — up to date
Bundled skill ✅ 23 files — up to date
Bundled schema ✅ Up to date
Type check ✅ No errors (all 10 packages)
Lint ✅ 0 errors, 0 warnings
Format ✅ All files formatted
Tests ✅ 111 test files, 0 failures (4 new regression tests in cleanup-service.test.ts)
  • Evidence provided: all 7 bun run validate checks passed; 4 new regression tests covering: master-branch-via-config, fallback-to-git-detection, whitespace-trimming, whitespace-only-fallback.
  • No checks skipped.

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No — loadRepoConfig reads .archon/config.yaml in the repo CWD, which the cleanup service already has access to.

Compatibility / Migration

  • Backward compatible? Yes — when worktree.baseBranch is not configured, behavior is identical to before.
  • Config/env changes? No new config keys; existing undocumented-but-documented worktree.baseBranch now actually works.
  • Database migration needed? No.

Human Verification (required)

  • Verified scenarios: type-check, lint, format, full test suite (including new regression tests) all pass via bun run validate.
  • Edge cases checked: whitespace-only baseBranch correctly falls through to git detection; loadRepoConfig returning {} (file absent) correctly falls through to git detection.
  • What was not verified: live end-to-end against a real master-branch repo — the regression tests cover this path at the unit level.

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: cleanup service only (runScheduledCleanup, getWorktreeStatusBreakdown, cleanupMergedWorktrees).
  • Potential unintended effects: loadRepoConfig is called once per tracked environment per cleanup cycle — this is a cheap local file read on a scheduled (non-hot) path, so performance impact is negligible.
  • Guardrails: existing per-environment try/catch in runScheduledCleanup continues to catch any loadRepoConfig parse errors and log them as env_cleanup_error (with a more useful message than before).

Rollback Plan (required)

  • Fast rollback: git revert 03fdd3e7 — single commit, no migration to undo.
  • Feature flags/config toggles: none required; the change activates only when worktree.baseBranch is set.
  • Observable failure symptoms: env_cleanup_error in server logs per tracked environment on startup (same as before the fix).

Risks and Mitigations

  • Risk: loadRepoConfig throws on a malformed .archon/config.yaml in a tracked repo.
    • Mitigation: The existing try/catch in runScheduledCleanup at line ~375 already catches per-environment errors and logs them. getWorktreeStatusBreakdown and cleanupMergedWorktrees propagate the error to their callers, which already handle it. No new unhandled throw paths.

Summary by CodeRabbit

  • New Features

    • Added configuration option to specify a base branch for repository cleanup operations. Falls back to git default branch detection if not explicitly configured.
  • Bug Fixes

    • Enhanced base branch resolution logic for scheduled cleanup tasks (issue #1419), including whitespace trimming and improved fallback handling.

…g.yaml (#1419)

The cleanup service called getDefaultBranch() three times without first
consulting the repo's .archon/config.yaml. On repos that use 'master'
(or any non-main default) without origin/HEAD set, this threw
env_cleanup_error once per tracked environment on every startup —
and the error's own remediation ("Set worktree.baseBranch in
.archon/config.yaml") was ineffective because that setting was never
read by this service.

Changes:
- Import loadRepoConfig in cleanup-service.ts
- Add private resolveBaseBranch(repoPath, cwd) helper that prefers
  config.worktree.baseBranch (trimmed) and falls back to getDefaultBranch
- Replace the three getDefaultBranch call sites in runScheduledCleanup,
  getWorktreeStatusBreakdown, and cleanupMergedWorktrees
- Mock loadRepoConfig in cleanup-service.test.ts and add four regression
  tests covering: master via config, whitespace-trimmed config value,
  fallback when no config, and whitespace-only config value

Fixes #1419
@Wirasm

Wirasm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Comprehensive PR Review

PR: #1925 · fix(cleanup): read worktree.baseBranch config before git default-branch detection
Reviewed by: code-review agent (partial — 1 of 5 agents produced findings)
Date: 2026-06-08


Summary

The fix is correct and minimal. The cleanup service was calling getDefaultBranch() unconditionally at 3 call sites, causing repeated env_cleanup_error logs on every startup for repos whose default branch isn't main. Worse, the error message pointed users to set worktree.baseBranch in config — a value the service never read, making it a dead end. The new resolveBaseBranch helper reads the config first and falls back to git detection only when nothing is configured. All 4 regression tests and the full bun run validate suite pass.

Verdict: APPROVE

Severity Count
🔴 CRITICAL 0
🟠 HIGH 0
🟡 MEDIUM 1
🟢 LOW 2

🟡 Medium Issues (Consider Before Merge)

Two beforeEach blocks not updated to reset mockLoadRepoConfig

📍 packages/core/src/services/cleanup-service.test.ts:841 and :935

After this PR, both getWorktreeStatusBreakdown and cleanupMergedWorktrees call resolveBaseBranch, which calls loadRepoConfig (mocked as mockLoadRepoConfig). Neither describe block's beforeEach was updated to clear and reset that mock. Tests currently pass because the module-level default returns {} (no baseBranch configured), which routes through the fallback — but any future test that uses mockLoadRepoConfig.mockResolvedValue(...) (sticky, not Once) in a prior suite will silently bleed into these two suites.

The two suites that were updated in this PR (removeEnvironment ~L118 and runScheduledCleanup ~L456) correctly add both resets. The fix is 4 lines mirroring the exact same pattern:

View fix
// cleanup-service.test.ts:841 — getWorktreeStatusBreakdown beforeEach
  beforeEach(() => {
    mockExecFileAsync.mockClear();
    mockGetDefaultBranch.mockClear();
    mockIsBranchMerged.mockClear();
    mockListByCodebaseWithAge.mockClear();
+   mockLoadRepoConfig.mockClear();
    mockGetDefaultBranch.mockResolvedValue('main');
    mockIsBranchMerged.mockResolvedValue(false);
+   mockLoadRepoConfig.mockResolvedValue({});
  });

// cleanup-service.test.ts:935 — cleanupMergedWorktrees beforeEach
  beforeEach(() => {
    // ... existing clears ...
    mockGetDefaultBranch.mockClear();
+   mockLoadRepoConfig.mockClear();
    mockGetDefaultBranch.mockResolvedValue('main');
+   mockLoadRepoConfig.mockResolvedValue({});
    // ... rest unchanged ...
  });

Options: Fix now (4 lines, mirrors existing pattern) | Create issue | Skip


🟢 Low Issues

View 2 low-priority observations
Issue Location Note
type RepoConfigForTest duplicates a subset of RepoConfig cleanup-service.test.ts:97 Narrow local type is a defensible test pattern; risk of divergence is very low given worktree.baseBranch stays optional
Comments say "getDefaultBranch returns 'main'" cleanup-service.test.ts:968,1005,1030 The path now goes through resolveBaseBranch → loadRepoConfig → getDefaultBranch; pre-existing style, not introduced by this PR

✅ What's Good

  • The resolveBaseBranch comment is one of the few places where a multi-line block comment is genuinely warranted: it names worktree.baseBranch in .archon/config.yaml is documented but never read — causes repeated env_cleanup_error on startup #1419, explains the historical dead-end, and documents the non-obvious constraint.
  • All 3 getDefaultBranch call sites updated consistently — no partial fix left behind.
  • Whitespace-only baseBranch trimming is correct: ' '.trim() is falsy, routing to git fallback as expected.
  • 4 regression tests cover all distinct paths: config-wins, fallback, whitespace-trim, whitespace-only-fallback.
  • Scope is truly minimal: no interface changes, no DB schema changes, no config key additions.
  • Full bun run validate passes on first run (7/7 checks).

Suggested Follow-up

…gedWorktrees

Both describe blocks now call resolveBaseBranch (which calls loadRepoConfig)
after the PR fix, but their beforeEach blocks weren't updated to clear and
reset mockLoadRepoConfig. Any future test using the sticky mockResolvedValue
form in a prior suite would silently bleed in. Added mockLoadRepoConfig.mockClear()
and mockLoadRepoConfig.mockResolvedValue({}) to both, matching the pattern
already applied to the removeEnvironment and runScheduledCleanup suites.

Also updated stale inline comments from "getDefaultBranch returns 'main'" to
"resolveBaseBranch returns 'main' (no config → getDefaultBranch fallback)"
to reflect the new indirection.
@Wirasm

Wirasm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

⚡ Self-Fix Report (Aggressive)

Status: COMPLETE
Pushed: ✅ Changes pushed to archon/task-fix-issue-1419
Commit: 9175b5c
Philosophy: Fix everything unless clearly a new concern


Fixes Applied (3 total)

Severity Count
🔴 CRITICAL 0
🟠 HIGH 0
🟡 MEDIUM 1
🟢 LOW 2
View all fixes
  • getWorktreeStatusBreakdown beforeEach not resetting mockLoadRepoConfig (cleanup-service.test.ts:841) — Added mockLoadRepoConfig.mockClear() + mockLoadRepoConfig.mockResolvedValue({}), matching the pattern already applied in the removeEnvironment and runScheduledCleanup suites
  • cleanupMergedWorktrees beforeEach not resetting mockLoadRepoConfig (cleanup-service.test.ts:935) — Same fix; eliminates sticky-mock bleed if a future test uses the non-Once variant
  • Stale "getDefaultBranch returns 'main'" comments (cleanup-service.test.ts:925,968,1005,1030) — Updated to "resolveBaseBranch returns 'main' (no config → getDefaultBranch fallback, default from beforeEach)" to reflect the new indirection introduced by this PR

Tests Added

(none — existing 46 tests pass; fix hardened their mock isolation)


Skipped

(none — all findings addressed)


Suggested Follow-up Issues

(none)


Validation

✅ Type check | ✅ Lint | ✅ Tests (46 passed)


Self-fix by Archon · aggressive mode · fixes pushed to archon/task-fix-issue-1419

@Wirasm Wirasm marked this pull request as ready for review June 8, 2026 18:59
@Wirasm Wirasm merged commit 67b8fd7 into dev Jun 8, 2026
3 checks passed
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b3c9ea5d-db78-4b82-a938-6a2214d75593

📥 Commits

Reviewing files that changed from the base of the PR and between 0ed4bb7 and c97527e.

📒 Files selected for processing (2)
  • packages/core/src/services/cleanup-service.test.ts
  • packages/core/src/services/cleanup-service.ts

📝 Walkthrough

Walkthrough

Cleanup service now respects the worktree.baseBranch configuration setting from .archon/config.yaml. A new resolveBaseBranch helper reads the configured value first, trims whitespace, and falls back to git default-branch detection if unset, eliminating repeated errors in repos where origin/HEAD is unavailable.

Changes

Base branch resolution from config

Layer / File(s) Summary
resolveBaseBranch helper and import
packages/core/src/services/cleanup-service.ts
New loadRepoConfig import and resolveBaseBranch(repoPath, cwd) helper that checks config for worktree.baseBranch (with trimming) and falls back to getDefaultBranch if unset.
Apply resolveBaseBranch to cleanup functions
packages/core/src/services/cleanup-service.ts
Replace getDefaultBranch calls with resolveBaseBranch in runScheduledCleanup, getWorktreeStatusBreakdown, and cleanupMergedWorktrees to use configured base branch when available.
Test mocking, assertions, and comment updates
packages/core/src/services/cleanup-service.test.ts
Mock config-loader, reset mocks in test beforeEach hooks, update existing test comments to reflect resolveBaseBranch behavior, and add comprehensive tests covering config-based selection, whitespace trimming, and git fallback scenarios.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A config-aware cleanup, at last!
No more errors when origin/HEAD's past,
Read baseBranch, trim spaces with care,
Then git detection awaits if not there! 🌿

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch archon/task-fix-issue-1419

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

worktree.baseBranch in .archon/config.yaml is documented but never read — causes repeated env_cleanup_error on startup

1 participant