Skip to content

Audit fixes: empty-dir handling, recursive scan, weave clean, hardened lock and hooks#10

Merged
PurpleReverie merged 3 commits into
mainfrom
audit/bug-hunt
May 31, 2026
Merged

Audit fixes: empty-dir handling, recursive scan, weave clean, hardened lock and hooks#10
PurpleReverie merged 3 commits into
mainfrom
audit/bug-hunt

Conversation

@PurpleReverie

Copy link
Copy Markdown
Owner

Summary

Bug audit pass triggered by the empty-child-dir hang on first init. Found and fixed several adjacent issues along the way. Single commit, fully tested end-to-end against real git repos.

Bug fixes

  • Empty placeholder dir on first init. When the target dir exists but has no `.git`, simple-git was walking up and running against the parent repo, so the child got reported as having "uncommitted-changes" (really the parent's). Now: empty dir → remove and clone; non-empty non-git dir → abort with a clear message so user data isn't wiped. Same guard added to `checkDirtyState` and `checkRepo`.
  • First clone landed on the remote's default branch instead of `thread.branch`. Pass `--branch` on clone so the child starts on the requested branch; later `checkout(hash)` for pinned threads still works because all refs are fetched.
  • `scan: ["."]` didn't find `.thread` files in subdirectories. Scan now recurses, skipping `.git`, `node_modules`, and any nested git repo (those are handled by `syncNestedThreads`). Results deduped by file path. README's documented `services/api.thread` layout now works with default config.

New: `weave clean`

Removes cloned child directories whose `.thread` file no longer exists.

  • Default: dry run, reports each orphan's status (clean / dirty / unpushed / non-git / missing) and what would happen.
  • `--apply`: actually remove clean orphans; refuse dirty/unpushed/non-git ones.
  • `--force`: remove even dirty/unpushed/non-git orphans (implies `--apply`).
  • Rebuilds the exclude block afterward to drop stale entries.

Ergonomic guards

  • `weave lock` refuses to pin unpushed commits. Uses `git branch -r --contains ` to verify the commit is reachable from a remote branch; otherwise coworkers can't sync. `--force` overrides.
  • `post-checkout` hook only fires on branch checkouts. Guards on `$3 = 1`; `git checkout README.md` no longer triggers `weave sync`.
  • `weave init` no longer exits silently when nothing is found. Names the scan paths and shows an example.

Cleanup

  • Consolidated four copies of `targetDirForThread` into `src/sync/targetDir.ts`. The previous `lastIndexOf('/')` implementation broke on Windows backslash paths; the shared helper uses `path.dirname`.

Test plan

End-to-end tests live at `tests/init-scenarios.sh` — no test framework, just bash + git + a local bare repo. 46 assertions across 17 scenarios.

  • `./tests/init-scenarios.sh` passes locally (46/46)
  • Reviewer: run `./tests/init-scenarios.sh` locally
  • Reviewer: manual sanity check on a real multi-repo setup if you have one handy

Deliberately out of scope

These came up in the brainstorm but weren't touched — call-out so they don't get lost:

  • `weave unlock` substring matching (behavior change risk)
  • Single-thread targeting / dry-run for sync / parallel pre-push (features, not bugs)
  • Detached-HEAD-after-unpin work-loss warning (UX needs care)
  • Tag-as-branch, submodule init, git LFS, upstream rename detection (feature surface)
  • Concurrent run locking, encoding handling, `.env` precedence, HTTPS prompt timeouts (orthogonal)

Removed command/script enumerations, file-by-file inventory, and
specific config values that mirror code — those will drift as the
project evolves. Kept conceptual descriptions and pointers to the
source-of-truth files. Also gitignore .claude/ so per-developer
agent config doesn't get committed.
…ned lock and hooks

Bug fixes
- syncRepo: when targetDir exists but has no .git, simple-git would walk
  up and run against the parent repo (reporting the parent's dirty state
  on the child). Now detect that case: remove if the dir is empty, else
  abort cleanly so user data isn't wiped.
- syncRepo: pass --branch on first clone so the child lands on
  thread.branch rather than the remote's default HEAD.
- checkDirtyState / checkRepo: guard against the same parent-walk by
  requiring .git in the target dir.
- scanThreadFiles: recurse into subdirectories (skipping .git,
  node_modules, and any nested git repo) so the README's documented
  layout works with default scan ['.']. Dedupe results by file path.

New: weave clean
- Diff the exclude block against current .thread files; for orphans,
  report status (clean / dirty / unpushed / non-git / missing).
- Default to dry run. --apply removes clean orphans, --force removes
  dirty/unpushed/non-git ones. Rebuilds the exclude block after.

Ergonomic guards
- weave lock: refuse to pin HEAD unless the commit is reachable from a
  remote branch (`git branch -r --contains`). Avoids the common footgun
  of pinning to a local-only commit no coworker can sync to. --force
  overrides.
- post-checkout hook: guard on $3 = 1 so it only fires on branch
  checkouts, not file-level `git checkout <file>`.
- weave init: when no .thread files are found, name the scan paths
  and show an example, instead of a bare "No .thread files found."

Cleanup
- Consolidate four copies of targetDirForThread into src/sync/targetDir.ts.
  The previous lastIndexOf('/') implementation broke on Windows paths;
  the shared helper uses path.dirname.

Tests
- tests/init-scenarios.sh exercises all of the above end-to-end against
  a local bare repo (no test framework dependency). 46 assertions
  across 17 scenarios; run with `./tests/init-scenarios.sh`.
simple-git < 3.36.0 has a high-severity RCE advisory. CI's
`npm audit --audit-level=high` step rejects the previous 3.33.0 pin.

Switched from exact pin to caret range so future patch releases of
simple-git flow in automatically — pinning is what allowed the
vulnerable version to linger here. Lockfile still pins exactly for
reproducible installs.

Verified: `npm audit --audit-level=high` reports 0 vulnerabilities;
`./tests/init-scenarios.sh` still 46/46 green.
@PurpleReverie PurpleReverie merged commit 762d410 into main May 31, 2026
1 check passed
@PurpleReverie PurpleReverie mentioned this pull request Jun 1, 2026
7 tasks
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