Skip to content

fix(pull-sdlc): validate worktree marker; keep self-update out of main#181

Merged
MarkMichaelis merged 1 commit into
mainfrom
fix/180-pullsdlc-stale-worktree
Jun 1, 2026
Merged

fix(pull-sdlc): validate worktree marker; keep self-update out of main#181
MarkMichaelis merged 1 commit into
mainfrom
fix/180-pullsdlc-stale-worktree

Conversation

@MarkMichaelis

Copy link
Copy Markdown
Contributor

Summary

Two distinct bugs surfaced together when running Pull-SDLC.ai.ps1 against a consumer repo whose .worktrees/sdlc-sync/ had been left behind by a different (or now-deleted) repo. Both are fixed here with behavior-first tests.

Bug 1 -- stale worktree marker treated as reusable

Invoke-AutoWorktreeSync reused any directory that contained a .git file, but the file's gitdir: target could belong to a foreign repo (or be deleted). Every subsequent git call inside the directory then failed with fatal: not a git repository: (NULL).

Fix. Validate by running git rev-parse --git-common-dir inside the worktree and compare against this repo's common-dir; if they don't match, the directory is discarded (git worktree remove --force + prune + filesystem Remove-Item) before creating a fresh worktree.

Bug 2 -- self-update silently dirtied the consumer's protected branch

Invoke-SelfRefresh used Move-Item to overwrite the on-disk script with upstream content -- on main. The working tree then carried Pull-SDLC.ai.ps1 as modified until the sync PR merged and the user pulled, and any local edits were silently destroyed.

Fix. The function now returns the temp-file path (string) instead of bool + in-place replace. Invoke-SelfRefreshGate forwards that temp path to Invoke-SelfReExec, which deletes the temp after the child exits. Upstream content reaches the on-disk script through the normal worktree-sync PR -- never through working-tree overwrites.

Tests

Full suite: 189 passed, 0 failed.

Manual verification against PSBitWarden

Before the fix:

Self-updated Pull-SDLC.ai.ps1 from D10ADEE to BD11F7E; re-running with original args
On protected branch 'main'. Switching to auto-worktree workflow ...
fatal: not a git repository: (NULL)
Reusing existing worktree '.worktrees/sdlc-sync' (clean).
fatal: not a git repository: (NULL)
... (cascade of failures)
Invoke-PullSDLC: ... You cannot call a method on a null-valued expression.

After the fix (same .worktrees/sdlc-sync/.git pointing to BitwardenCleaner's gitdir):

On protected branch 'main'. Switching to auto-worktree workflow ...
Discarding stale worktree directory '.worktrees/sdlc-sync' (marker does not belong to this repo).
Creating worktree '.worktrees/sdlc-sync' on 'chore/sdlc-sync' from 'main' ...
Running sync inside worktree (branch 'chore/sdlc-sync') ...

(The run then surfaced a separate, legitimate policy violation about a local edit to tasks/README.md.template -- unrelated to this fix.)

Closes #180

Two distinct bugs surfaced together when running Pull-SDLC.ai.ps1 against
a consumer repo whose .worktrees/sdlc-sync/ had been left behind by a
different (or now-deleted) repo.

1. Stale worktree marker treated as reusable. Invoke-AutoWorktreeSync
   reused any directory that contained a .git FILE, but the file's
   `gitdir:` target could belong to a foreign repo. Every subsequent
   git call inside then failed with`fatal: not a git repository: (NULL)`.
   Now validate by running `git rev-parse --git-common-dir` inside the
   worktree and comparing against this repo's common-dir; if they don't
   match, the directory is discarded (`git worktree remove --force` +
   `prune` + filesystem rm) before creating a fresh worktree.

2. Self-update silently dirtied the consumer's protected branch.
   Invoke-SelfRefresh used Move-Item to overwrite the on-disk script
   with upstream content -- on `main`. The working tree then carried
   `Pull-SDLC.ai.ps1` as modified until the sync PR merged and the
   user pulled, and any local edits were silently destroyed. Now the
   function returns the temp-file path (string) instead of bool +
   in-place replace; Invoke-SelfRefreshGate forwards that temp path to
   Invoke-SelfReExec, which deletes the temp after the child exits.
   Upstream content reaches the on-disk script through the normal
   worktree-sync PR -- never through working-tree overwrites.

Both fixes are covered by behavior-first tests; existing self-refresh
and auto-worktree tests updated for the new bool->string contract.

Closes #180

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MarkMichaelis MarkMichaelis merged commit 35b18fe into main Jun 1, 2026
2 checks passed
@MarkMichaelis MarkMichaelis deleted the fix/180-pullsdlc-stale-worktree branch June 1, 2026 00:02
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.

fix(pull-sdlc): stale worktree marker + self-update dirties consumer main

1 participant