fix(process): pin process-state writes to canonical BotRoot, not worktree - #637
Conversation
…tree Write-ProcessFile and Test-ProcessStopSignal resolved BotRoot via $PWD whenever no -BotRoot was passed. During task execution, Invoke-WorkflowProcess.ps1 Push-Location's into the task's worktree, whose .bot/.control is a junction back to the canonical .control dir. Worktree teardown (Complete-TaskWorktree, Reset-TaskWorktree, Remove-OrphanWorktrees) removes that junction before removing the worktree itself, so a heartbeat write racing the teardown resolves to a path that no longer exists and silently drops after exhausting retries. Pin every process-registry write inside the worktree-scoped execution window to $botRoot, the canonical root captured once at process start and never reassigned. Thread the same canonical root through Invoke-TaskClarificationLoopIfPresent via a new -ProcessBotRoot parameter, kept separate from its existing worktree-scoped -BotRoot (used for the answers-file path). Also add a processes/ directory guard to Write-ProcessFile, mirroring Write-ProcessActivity, so a missing directory self-heals instead of failing outright. Closes andresharpe#612
There was a problem hiding this comment.
Pull request overview
This PR fixes a race where workflow process-state/heartbeat writes could resolve through a task worktree’s transient .bot/.control junction (when Write-ProcessFile/Test-ProcessStopSignal fall back to $PWD), causing state updates to be dropped during worktree teardown. It pins process-registry reads/writes to the canonical project bot root during the worktree-scoped execution window, and hardens Write-ProcessFile by ensuring the processes/ directory exists before writing.
Changes:
- Pin
Write-ProcessFile/Test-ProcessStopSignalcalls inside the worktreePush-Location→Pop-Locationwindow to the canonical$botRoot. - Thread the canonical root into the task clarification loop via a new
-ProcessBotRootparameter (kept separate from the existing worktree-scoped-BotRootused for answers pathing). - Add a
processes/directory existence guard toWrite-ProcessFileto self-heal missing directories.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/runtime/Scripts/Invoke-WorkflowProcess.ps1 | Pins process stop-signal checks and process-state writes to the canonical bot root during worktree execution; threads canonical root into the clarification loop to avoid resolving via the transient worktree junction. |
| src/runtime/Modules/Dotbot.Process/Dotbot.Process.psm1 | Ensures processes/ exists before writing process-state files, preventing failures when the directory is missing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
carlospedreira
left a comment
There was a problem hiding this comment.
Looks good. I reviewed the process-registry/worktree-rooting changes and ran focused validation against the PR commit: parser check on the changed PowerShell files plus tests/Test-ProcessRegistry.ps1 (20/20 passing). I don’t see a merge blocker.
Linked issue
Closes #612
Summary of changes
Write-ProcessFileandTest-ProcessStopSignalresolveBotRootvia$PWDwhenever no
-BotRootis passed. During task execution,Invoke-WorkflowProcess.ps1Push-Locations into the task's worktree, whose.bot/.controlis a junction back to the canonical.controldir. Worktreeteardown (
Complete-TaskWorktree,Reset-TaskWorktree,Remove-OrphanWorktrees)removes that junction before removing the worktree itself, so a heartbeat write
racing the teardown resolves to a path that no longer exists and silently drops
after exhausting retries (
Could not find a part of the path '…\worktrees\1\task-…\.bot\.control\processes\proc-….json.tmp').Changes:
to
$botRoot— the canonical root captured once at process start and neverreassigned. Covers all
Write-ProcessFile/Test-ProcessStopSignalcallsbetween the worktree
Push-Locationand itsPop-Location.Invoke-TaskClarificationLoopIfPresentvia a new
-ProcessBotRootparameter, kept deliberately separate from itsexisting worktree-scoped
-BotRoot(which is still used for the answers-filepath).
processes/directory guard toWrite-ProcessFile, mirroringWrite-ProcessActivity, so a missing directory self-heals instead of failingoutright.
Process writes outside the worktree window (CWD = project root) are intentionally
left unpinned — they already resolve to the canonical root. Sibling runners
(
Invoke-PromptProcess.ps1,Invoke-DotbotProcess.ps1) neverPush-Locationinto a worktree, so they are unaffected. Activity-log writes go through
Write-BotLog, which is init-pinned to the canonical control dir at startup, sothey were never vulnerable to this race.
Testing notes
pwshparse check on both changed files — clean.tests/Test-ProcessRegistry.ps1— 20/20 pass.tests/Test-Compilation.ps1— 372 pass, 0 fail..controljunction:a
BotRoot-pinned write reaches the canonicalprocesses/dir and survivesjunction teardown, the directory guard recreates a deleted
processes/, whilean unpinned write (the old behaviour) resolves to the now-orphaned worktree path
and never reaches canonical — confirming both the bug and the fix.
Checklist