fix(worktree): resolve the task integration target instead of trusting the recorded base_branch - #688
Open
EnmaJim wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked issue
Closes #679
Closes #680
Related: #676 (a task stuck after a merge failure). This change removes the most common cause of that
merge failure but does not touch the escalation/skip mechanism itself, so it is not claimed as closed.
Summary of changes
Complete-TaskWorktreetook its integration target from thebase_branchthatworktree-map.jsonrecorded when the task worktree was created, and never reconciled it with thebranch the main checkout was actually on. Two consequences, both reproducible without an AI provider:
plain
success: True / "Squash-merged to master and cleaned up", while the run was onworkflow/plan-feature-XXXX. The trunk was written without operator intent, silently, and theworking copy was left on the trunk.
.bot/workspace/decisions/— the pre-merge stash excluded that tree, sothe checkout failed and the run halted with
Failed to checkout master … (currently on: workflow/…)and no stated cause.
decisions/is tracked and is committed by this same function, but unlike.bot/workspace/tasks/it was never scrubbed or backed up, so a dirty file there defeated the verycheckout the stash exists to enable. Recovery was impossible without hand-editing the map:
New-TaskWorktreeonly writes an entry for a task id it has not seen, sobase_branchwas neverrefreshed and "investigate and retry" replayed the identical failure.
The target is now resolved by an explicit precedence, in a new private
Resolve-TaskMergeTarget:-BaseBranch— a workflow run passes its integration branch, so the target is statedrather than inferred;
git.base_branch— an operator declaration, delegated toResolve-DotbotBaseBranchsoa configured-but-missing branch still fails fast (Per-run integration branch + configurable base branch (provider-agnostic core) #466);
task/*branch, neither of which is a validintegration target;
Resolve-MainBranch, when nothing was recorded.Adoption lives in
Complete-TaskWorktreeand deliberately not inResolve-MainBranch/Resolve-DotbotBaseBranch— those stay HEAD-blind so they remain safe to call while the main repo is ona task branch (#317).
Three defects in the same paths are fixed alongside it, because the first fix is unsafe without them:
.bot/workspace/decisions/. Only.bot/workspace/tasks/is excluded now, and only because it is scrubbed immediately above andrestored from the task-state backup.
git stash push's exit code.git stash push -ucan stashsuccessfully and still exit 1 over an advisory —
The following paths are ignored by one of your .gitignore files: .bot/workspace/tasks, emitted because the worktree execution environment adds thatpath to
.git/info/exclude.$wasStashedwas computed from that exit code, so the pop was skippedand the operator's uncommitted work was silently parked in a stash. Detection now compares
refs/stashbefore and after. Without this, removing thedecisions/exclusion would have traded ablocked run for silently parked work — strictly worse.
Assert-OnBaseBranchreports why a checkout failed, and can honourgit.base_branch. It pipedgit's stderr to
Out-Null, so every blocker — a dirty tracked file, a branch held by another linkedworktree, a file lock — produced the same unactionable message. It also had no
-BotRootparameter,so its no-
-BranchNamefallback could only ever resolvemain/master; the three cleanup call sitesin
Invoke-WorkflowProcess.ps1(:1774,:2390,:2413) used that fallback and yanked the workingcopy off a configured base branch on any failed or skipped task. One of those sites carries the
comment
# Re-assert base branch after failed-task cleanup (Fix: wrong-branch merge).Update-TaskWorktreeBaseBranchis exported so the runner can reconcile a stale record when it reuses aworktree created by an earlier run, rather than duplicating the map-locking logic.
Note for the reviewer
Three places still describe the pre-
fe69737behaviour —Dotbot.Worktree.psm1's module synopsis,AGENTS.md:71, andREADME.md:21all say the task branch is "squash-merged to main". That was alreadyimprecise once per-run integration branches landed, and this change widens the gap. Left untouched here
to keep the diff to one concern; happy to correct them in this PR or a follow-up, whichever you prefer.
Testing notes
28 new assertions in
tests/Test-Components.ps1, placed with the existingComplete-TaskWorktreecoverage. They cover adoption (target, blast radius, trunk untouched, checkout preserved, map
reconciled), explicit
-BaseBranchprecedence, the#466configured-base precedence, thetask/*guard, the stash round-trip — including that no stash is left behind and the operator's dirty content is
restored — and
Assert-OnBaseBranch, which had zero coverage before this change despite being thesource of the reported failure.
Results on Windows 11 / pwsh 7.6.4 / git 2.54.0:
03-check-md-refs.ps1Test-Compilationexport alignmentPre-existing guards re-confirmed green rather than adjusted:
#317(Resolve-MainBranchnever readsHEAD;
Get-BaseBranchstays deleted; the E2E test that a task worktree forks frommain's tip whileHEAD is on a feature branch),
#466,#517,#570,#655, the detached-HEAD completion test, theunborn-
maincompletion tests, and themerge --squash/ patch-pathspec string-literal greps.Verified end to end against a real project
A 1129-file polyglot repo (.NET 10 + React 19 + Astro + Node gateways), driving the original
reproductions:
Squash-merged to master;masteradvanced; HEAD yanked tomastermasterbyte-identical; HEAD preserved; reconciliation named in the messagedecisions/failure_kind: exception,Failed to checkout master … (currently on: workflow/…), task parkedsuccess: True; the dirty content restored verbatim; no stash left behind… (currently on: probe-branch): fatal: 'master' is already used by worktree at '…'git.base_branchset, failure-path cleanupmastergit.base_branchset, happy pathA real
fast-promptrun on that project still merges into its integration branch with the same 2-fileblast radius,
masteruntouched, HEAD restored, no stashes or worktrees left behind — and the project'sown suite still passes 370/370, matching the pre-change baseline exactly.
Two things worth knowing, both caught by that verification and fixed before this PR:
Write-BotLogon the happy path broke 17 merge tests.Dotbot.Worktreedoes not depend onDotbot.Logging, soWrite-BotLogexists only when the runtimeprocess happens to have loaded it — every pre-existing call in the module sits on an error path, which
is why it had never mattered. The call is now guarded with
Get-Command, matching howResolve-DotbotBaseBranchguardsGet-MergedSettings. Unguarded it would also have broken the MCPtask-submit-reviewand UITaskAPIretry paths, which callComplete-TaskWorktreefrom hosts thatdo not load the logger.
git.base_branchwhenever the checkouthappened to sit elsewhere, putting the work on
masterand breaking the documented workaround forthis very bug. Hence precedence step 2, now pinned by its own test.
Checklist
CHANGELOG.mdunder[Unreleased]→### Fixed; see thereviewer note above for three pre-existing "squash-merged to main" statements left untouched
base_branch, silently writing to the trunk or blocking the run #679 andAssert-OnBaseBranchdiscards git's error and cannot readgit.base_branch#680