Skip to content

2.0.0-rc.16: the worktree audit reads paths with spaces, and --bootstrap never writes through a symlinked directory - #36

Open
J0UH wants to merge 3 commits into
chhoumann:mainfrom
J0UH:fix/worktree-paths-and-sync-symlinks
Open

J0UH wants to merge 3 commits into
chhoumann:mainfrom
J0UH:fix/worktree-paths-and-sync-symlinks

Conversation

@J0UH

@J0UH J0UH commented Oct 21, 2026

Copy link
Copy Markdown

Two bugs in the port's own code, fixed with a failing test for each, then released as 2.0.0-rc.16. The pins stay at 88d4751.

worktree-audit.sh drops every worktree under a path with a space

The audit read each worktree path from git worktree list --porcelain with awk's $2, which stops at the first space. When the checkout and its worktrees share a parent such as /work/My Project/, the main worktree and every other one all read as /work/My. Line 92 then skips each one as the main worktree, and the table comes out empty with exit 0.

Reproduction on main, with a repo and two worktrees under <tmp>/My Project/:

$ bash worktree-audit.sh
warn: no -b given; MERGED checks against origin/main
# base: origin/main
SIZE	AGE	MERGED	DIRTY	REMOTE	PR	LAST_CHAT	BUCKET	WORKTREE

After the fix:

SIZE	AGE	MERGED	DIRTY	REMOTE	PR	LAST_CHAT	BUCKET	WORKTREE
4.0K	0d	YES	clean	no-remote	-	-	safe	/private<tmp>/My Project/b
4.0K	0d	YES	clean	no-remote	-	-	safe	/private<tmp>/My Project/a

Both awk calls now print everything after worktree , and the loop reads with IFS= read -r. The audit is a declared fork, so its tools/forks.json entry now names this change. A path that contains a newline would still break the line-based table, so I left NUL-delimited parsing out.

New test in tests/worktree-audit.test.mjs: "lists every worktree when the checkout and its worktrees sit under a path with spaces". On main it fails with Received: [].

--bootstrap writes through a symlinked local directory

bootstrapComponent ran lstat only on the last part of each path. With plugins/pstack/skills/arena linked to a directory outside the checkout, arena/SKILL.md looked like a plain file, and bootstrap overwrote the file behind the link.

Reproduction on main:

$ bun -e "...bootstrapComponent({ newDir, localDir, rules: [] })"
{"written":["skills/arena/SKILL.md"],"kept":[],"symlinks":[],"hits":[]}
outside/arena/SKILL.md now: upstream

After the fix:

{"written":[],"kept":[],"symlinks":["skills/arena"],"hits":[]}
outside/arena/SKILL.md now: outside

The normal sync already reports a local link at a directory above an upstream path and writes nothing under it. Bootstrap now does the same: it reports the first symlinked parent of each carried path under symlinks, which the CLI prints as FAIL: symlink at plugins/pstack/skills/arena and which blocks every write. dirsAbove moves to module scope so both paths share it.

New test in tests/sync.test.mjs: "bootstrapComponent > a local symlink at a directory above upstream paths is reported at the link, and nothing is written through it". On main it fails with Received: [] for report.symlinks.

Release

VERSION, the manifests, and package.json move to 2.0.0-rc.16. CHANGES.md gets the entry and its table, and the README's pinned installer command names the new tag. Neither hooks file changes.

Checks

  • GIT_CONFIG_GLOBAL=/dev/null bun test tests/ --timeout 30000: 552 pass, 46 skip, 0 fail
  • bun tools/generate.mjs --check: 17 generated files current, all checks pass
  • Scripts job: bun run typecheck clean, bun test orch watch-pr check-plan.test.ts 119 pass, 0 fail
  • bun tools/sync.mjs {pstack,cursor-team-kit,thermos} 88d4751 --dry-run: exit 0 for all three. pstack lists worktree-audit.sh as a declared policy fork (76 changed lines)
  • shellcheck worktree-audit.sh: clean
  • git diff --check: clean

J0UH added 3 commits October 21, 2026 10:46
The audit took each worktree path from `git worktree list --porcelain`
with awk's $2, which stops at the first space. With the checkout and
its worktrees under one parent such as "/work/My Project/", the main
worktree and every other one all read as "/work/My", so the loop
skipped each of them as the main worktree and printed an empty table.

The audit now prints everything after "worktree " and reads it with
IFS= read -r. The new test puts the fixture under a temp directory
whose name has a space: before the fix it found no worktrees, now it
finds both. The forks.json entry for the audit names the change.
bootstrapComponent checked only the final path component with lstat.
With plugins/pstack/skills/arena a symlink to a directory outside the
checkout, arena/SKILL.md looked like a regular file and --bootstrap
overwrote the file the link points at.

The normal sync already reports a local link at a directory above an
upstream path and writes nothing under it. Bootstrap now does the
same: before any write it reports the first symlinked ancestor of each
carried path under symlinks, which fails the run and blocks every
write. dirsAbove moves to module scope so both paths share it.
VERSION, the manifests, and package.json move to 2.0.0-rc.16. CHANGES.md
gets the entry with its behavior-delta table, and the README's pinned
installer command names the new tag. The pins stay at 88d4751, so
NOTICE.md, NOTICE-skills.md, and poteto-mode's copy of the notice do not
change. The skill count stays 64.
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.

1 participant