fix: syncDirectory same-path guard, nested .git exclusion, and sync stash handling #356

Merged
jason.woltje merged 2 commits from fix/idempotent-init into main 2026-04-03 01:42:19 +00:00
Owner

Follow-up to #355

Two runtime bugs discovered after merging the idempotent init PR.

Bug 1: mosaic wizard — EACCES on same-path sync (file-ops.ts)

EACCES: permission denied, copyfile '.git/objects/pack/pack-XXX.idx' -> same path

Cause: syncDirectory called where sourceDir === mosaicHome. It copied every file onto itself; git pack files are read-only (0444), so copyFileSync fails.

Also: excludeGit only matched top-level .git — nested dirs like sources/agent-skills/.git were still traversed.

Fix (packages/mosaic/src/platform/file-ops.ts):

  • Early return when resolve(source) === resolve(target)
  • Skip .git dirs at any depth (not just top-level)
  • Skip files inside .git/ paths

Tests (packages/mosaic/__tests__/platform/file-ops.test.ts):

  • Same-path no-op (with read-only git pack files)
  • Nested .git exclusion
  • .git copied when excludeGit: false
  • Preserve option respected

Bug 2: mosaic sync — dirty worktree breaks pull (mosaic-sync-skills)

error: cannot pull with rebase: You have unstaged changes.

Cause: Bare git pull --rebase with no dirty-tree handling.

Fix (packages/mosaic/framework/tools/_scripts/mosaic-sync-skills):

  • Detect dirty index/worktree via git diff --quiet
  • Auto-stash before pull, restore after
  • Graceful pull failure (warn + continue with existing checkout)
  • Warn on stash pop conflicts instead of crashing

Testing

pnpm --filter @mosaic/mosaic test   # 41 passed (8 files)
pnpm typecheck                      # all passed
pnpm lint                           # all passed
pnpm format:check                   # all passed

@mosaic/mosaic bumped to 0.0.3-1

## Follow-up to #355 Two runtime bugs discovered after merging the idempotent init PR. ### Bug 1: `mosaic wizard` — EACCES on same-path sync (file-ops.ts) ``` EACCES: permission denied, copyfile '.git/objects/pack/pack-XXX.idx' -> same path ``` **Cause:** `syncDirectory` called where `sourceDir === mosaicHome`. It copied every file onto itself; git pack files are read-only (`0444`), so `copyFileSync` fails. **Also:** `excludeGit` only matched top-level `.git` — nested dirs like `sources/agent-skills/.git` were still traversed. **Fix (`packages/mosaic/src/platform/file-ops.ts`):** - Early return when `resolve(source) === resolve(target)` - Skip `.git` dirs at any depth (not just top-level) - Skip files inside `.git/` paths **Tests (`packages/mosaic/__tests__/platform/file-ops.test.ts`):** - Same-path no-op (with read-only git pack files) - Nested `.git` exclusion - `.git` copied when `excludeGit: false` - Preserve option respected ### Bug 2: `mosaic sync` — dirty worktree breaks pull (mosaic-sync-skills) ``` error: cannot pull with rebase: You have unstaged changes. ``` **Cause:** Bare `git pull --rebase` with no dirty-tree handling. **Fix (`packages/mosaic/framework/tools/_scripts/mosaic-sync-skills`):** - Detect dirty index/worktree via `git diff --quiet` - Auto-stash before pull, restore after - Graceful pull failure (warn + continue with existing checkout) - Warn on stash pop conflicts instead of crashing ### Testing ``` pnpm --filter @mosaic/mosaic test # 41 passed (8 files) pnpm typecheck # all passed pnpm lint # all passed pnpm format:check # all passed ``` `@mosaic/mosaic` bumped to `0.0.3-1`
jason.woltje added 2 commits 2026-04-03 01:41:59 +00:00
Two bugs causing 'EACCES: permission denied, copyfile' when source
and target are the same path (e.g. wizard with sourceDir == mosaicHome):

1. No same-path guard — syncDirectory tried to copy every file onto
   itself; git pack files are read-only (0444) so copyFileSync fails.
2. excludeGit only matched top-level .git — nested .git dirs like
   sources/agent-skills/.git were copied, hitting the same permission
   issue.

Fixes:
- Early return when resolve(source) === resolve(target)
- Match .git dirs at any depth via dirName and relPath checks
- Skip files inside .git/ paths

Added file-ops.test.ts with 4 tests covering all cases.
fix: mosaic sync — auto-stash dirty worktree before pull --rebase
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
e83674ac51
git pull --rebase fails with 'cannot pull with rebase: You have
unstaged changes' when the skills repo has local modifications.

Fix: detect dirty index/worktree, stash before pull, restore after.
Also gracefully handle pull failures (warn and continue with existing
checkout) and stash pop conflicts.
jason.woltje merged commit 0be9729e40 into main 2026-04-03 01:42:19 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#356