Skip to content

fix(process): pin process-state writes to canonical BotRoot, not worktree - #637

Merged
carlospedreira merged 1 commit into
andresharpe:mainfrom
emrahzunicaplab:bugfix/heartbeat-worktree-junction-race
Jul 11, 2026
Merged

fix(process): pin process-state writes to canonical BotRoot, not worktree#637
carlospedreira merged 1 commit into
andresharpe:mainfrom
emrahzunicaplab:bugfix/heartbeat-worktree-junction-race

Conversation

@emrahzunicaplab

Copy link
Copy Markdown
Contributor

Linked issue

Closes #612

Summary of changes

Write-ProcessFile and Test-ProcessStopSignal resolve BotRoot via $PWD
whenever no -BotRoot is passed. During task execution,
Invoke-WorkflowProcess.ps1 Push-Locations 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 (Could not find a part of the path '…\worktrees\1\task-…\.bot\.control\processes\proc-….json.tmp').

Changes:

  • Pin every process-registry write inside the worktree-scoped execution window
    to $botRoot — the canonical root captured once at process start and never
    reassigned. Covers all Write-ProcessFile / Test-ProcessStopSignal calls
    between the worktree Push-Location and its Pop-Location.
  • Thread the same canonical root through Invoke-TaskClarificationLoopIfPresent
    via a new -ProcessBotRoot parameter, kept deliberately separate from its
    existing worktree-scoped -BotRoot (which is still used for the answers-file
    path).
  • Add a processes/ directory guard to Write-ProcessFile, mirroring
    Write-ProcessActivity, so a missing directory self-heals instead of failing
    outright.

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) never Push-Location
into a worktree, so they are unaffected. Activity-log writes go through
Write-BotLog, which is init-pinned to the canonical control dir at startup, so
they were never vulnerable to this race.

Testing notes

  • pwsh parse check on both changed files — clean.
  • tests/Test-ProcessRegistry.ps1 — 20/20 pass.
  • tests/Test-Compilation.ps1 — 372 pass, 0 fail.
  • Reproduced the exact Write-ProcessFile targets transient task worktree for global process state → fails during worktree churn #612 scenario in a sandbox with a real .control junction:
    a BotRoot-pinned write reaches the canonical processes/ dir and survives
    junction teardown, the directory guard recreates a deleted processes/, while
    an unpinned write (the old behaviour) resolves to the now-orphaned worktree path
    and never reaches canonical — confirming both the bug and the fix.

Checklist

…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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-ProcessStopSignal calls inside the worktree Push-LocationPop-Location window to the canonical $botRoot.
  • Thread the canonical root into the task clarification loop via a new -ProcessBotRoot parameter (kept separate from the existing worktree-scoped -BotRoot used for answers pathing).
  • Add a processes/ directory existence guard to Write-ProcessFile to 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 carlospedreira left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@carlospedreira
carlospedreira merged commit ae2e50e into andresharpe:main Jul 11, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Inbox to Done in Dotbot Product Backlog Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Write-ProcessFile targets transient task worktree for global process state → fails during worktree churn

3 participants