v0.42.33.0 fix(sources): confine sync re-clone to gbrain-owned clones; never delete a user working tree (#1881)#1960
Merged
Merged
Conversation
…ete a user working tree (#1881) recloneIfMissing deleted local_path whenever a source had a remote_url and a non-healthy on-disk state, with no check that gbrain actually created the clone. A source whose local_path was a user's live working tree (remote_url set, no gbrain-created clone) could have its directory removed and re-cloned over. - isOwnedClone(): ownership, not path-containment. True only for a config .managed_clone marker (written by addSource --url) or exact normalized-path equality with defaultCloneDir(id) (back-compat for pre-marker default clones). - recloneIfMissing: ownership guard aborts before ANY filesystem op; EXDEV-safe sibling-temp clone + atomic swap (old aside -> new in -> drop old) with best-effort restore + a message naming where the original is preserved; symlink-leaf reject before the destructive rename. - sync.ts validate_repo_state guards reclone on isOwnedClone (no per-sync warn). - sources restore degrades to a warning for an unowned source instead of the misleading "missing clone, try sync" hint. Tests: #1881 regression (tree survives), isOwnedClone matrix, symlink reject, EXDEV swap residue-free, --clone-dir owned-via-marker, restore CV3, unownedHint healthy/degraded, sync-level refusal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#1881) Add the missing src/core/sources-ops.ts entry to KEY_FILES.md capturing the must-never-violate reclone-ownership guarantee: gbrain only deletes/re-clones a clone it created (isOwnedClone), never a user working tree. Covers managed_clone marker, defaultCloneDir back-compat, EXDEV-safe swap, TOCTOU + symlink-leaf guards, unmanaged_path SourceOpError, and the read-only sources restore path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rmrf-fix # Conflicts: # CHANGELOG.md # VERSION # package.json
mgunnin
added a commit
to mgunnin/gbrain
that referenced
this pull request
Jun 9, 2026
* upstream/master: v0.42.37.0 fix(security,ingest): source-isolation grant enforcement + non-string frontmatter guard + papercuts (garrytan#1999) v0.42.36.0 fix(sync): resumable, durable, single-flight sync — converges under pool exhaustion + repeated kills (garrytan#1794) (garrytan#1980) v0.42.35.0 fix(sync): recover from unreachable last_commit instead of full-walking forever (garrytan#1970) (garrytan#1975) v0.42.34.0 feat(search): typed-edge relational retrieval — relationship questions get relationship answers (garrytan#1959) docs(designs): add COMMUNITY_IDEAS ledger from open-PR backlog triage (garrytan#1969) v0.42.33.0 fix(sources): confine sync re-clone to gbrain-owned clones; never delete a user working tree (garrytan#1881) (garrytan#1960) # Conflicts: # src/core/operations.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1881 —
gbrain sync --strategy codedeleted a user's live git working tree.The bug:
recloneIfMissing(src/core/sources-ops.ts) deletedlocal_pathwhenever a source had aremote_urland a non-healthy on-disk state, with no check that gbrain actually created the clone. A source whoselocal_pathpointed at a user's working tree (the federated shape aremote_url+ external path produces) could have its directory removed and re-cloned over.The fix — ownership, not path-containment:
isOwnedClone()— gbrain re-clones/deletes only a clone it created, proven by aconfig.managed_clonemarker (written byaddSource --url) or exact normalized-path equality withdefaultCloneDir(id)(back-compat for pre-marker default-location clones). Everything else is fail-closed (refused).recloneIfMissing— ownership guard aborts before any filesystem op (newunmanaged_pathSourceOpError); EXDEV-safe sibling-temp clone + atomic swap (old aside → new in → drop old) with best-effort restore and an error that names where the original is preserved; symlink-leaf reject before the destructive rename.sync.tsvalidate_repo_state guards the reclone onisOwnedClone(no per-sync warning — surfacing lives in a follow-up doctor check).sources restoredegrades to a warning for an unowned source instead of the misleading "missing clone, try sync" hint.Reported by @zaqwery. The ownership-invariant correction came out of a cross-model plan + diff review (Codex outside-voice).
Test Coverage
AI-assessed coverage of the changed paths: 85% (gate PASS). The 2 untested branches are a deliberately-defensive unreachable TOCTOU re-check and a hard-to-provoke rename-failed restore catch.
Pre-Landing Review
Pre-landing review: ship as-is (no issues). Claude + Codex adversarial both flagged one item (lexical
resolvePathownership + symlinked clones-ancestor). Investigated: the proposed realpath-chain fix false-positives on system symlinks (macOS/var→/private/var) and doesn't address the real residual, which is DB-trust (a forgedmanaged_clonemarker), not path confusion — for an owned clone gbrain created the dir there (cloneReporefuses a non-empty dest). Kept the leaf-symlink TOCTOU guard, improved the failed-swap message to name the preserved original, and filed the residual as a P2 TODO (CI guard that onlyaddSourcewrites the marker + an on-disk ownership stamp).Design Review
No frontend files changed — design review skipped.
Eval Results
No prompt-related files changed — evals skipped.
Plan Completion
All plan items DONE (eng-review plan + all accepted CV findings). Plan + GSTACK REVIEW REPORT at
~/.claude/plans/.Verification Results
No dev server / web UI — CLI tool. Verified via the unit suite and the #1881 repro encoded as a regression test (federated row → throws
unmanaged_path, working tree + sentinel survive).TODOS
Filed 5 follow-ups (gbrain#1881 section): doctor misconfig check (P2), harden
managed_cloneagainst forgery + on-disk stamp (P2),.gbrain-reclone-*orphan sweep (P3),--clone-dirpolicy / dormantclone_dir_outside_gbrain(P3), CLIsources removeclone-storage leak (P3).Documentation
docs/architecture/KEY_FILES.md: added the missingsrc/core/sources-ops.tsentry capturing the v0.42.33.0 (gbrain sync --strategy code deleted the working directory (federated source with local_path, no --url) #1881) reclone-ownership invariant — gbrain only deletes/re-clones a clone it created (isOwnedClone:config.managed_clonemarker ordefaultCloneDirexact-match), never a user working tree. Covers the fail-closedunmanaged_pathSourceOpError, EXDEV-safe sibling-temp swap, TOCTOU + symlink-leaf guards, sharedunownedHint, and the read-onlygbrain sources restorepath.Test plan
think-gateway-adapter/conversation-parser/llm-*) — not in this branch's files; environment-dependent (a local AI-gateway credential makes the adapter build a client withANTHROPIC_API_KEYunset); passes in clean CI.🤖 Generated with Claude Code