Summary
gstack-gbrain-sync writes .gbrain-source to the consuming repo's root after gbrain sources attach <id> succeeds, but it doesn't add .gbrain-source to that repo's .gitignore. The v1.29.0.0 changelog explicitly promised this:
.gbrain-source added to .gitignore so per-worktree pin doesn't leak across branches.
The change actually shipped only added .gbrain-source to gstack's own .gitignore, not the consumer repo's. Without the consumer-side gitignore entry, the pin file gets committed and breaks the per-worktree promise: Conductor sibling worktrees of the same repo + branch step on each other's pin every time anyone commits.
Repro
cd ~/some-repo # any repo with no prior .gbrain-source line
/sync-gbrain # via gstack v1.29.0.0+ on a gbrain-configured machine
git status
# → Untracked files:
# .gbrain-source
grep -F '.gbrain-source' .gitignore || echo "MISSING — should have been auto-appended"
# → MISSING — should have been auto-appended
Then if a teammate (or sibling Conductor worktree) commits unrelated work with git add -A or includes the pin in a commit by accident, the pin propagates to other branches/worktrees and silently overrides their own pin on next checkout.
Suggested fix
In bin/gstack-gbrain-sync.ts:runCodeImport, right after gbrain sources attach <id> succeeds (around line 336-351 per the v1.29.0.0 path-hash work), append .gbrain-source to <root>/.gitignore if not already present:
// After successful attach, append to consumer repo's .gitignore (idempotent).
// Mirrors the same maintenance contract the v1.29.0.0 changelog claimed for
// gstack's own .gitignore: per-worktree pin must not leak across branches,
// which means it must be ignored in the consuming repo too, not just gstack.
const gitignorePath = join(root, ".gitignore");
let existing = "";
try { existing = readFileSync(gitignorePath, "utf-8"); } catch { /* fine, we'll create */ }
if (!existing.split("\n").some(line => line.trim() === ".gbrain-source")) {
const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
writeFileSync(gitignorePath, existing + sep + ".gbrain-source\n");
}
Wrap in try/catch — if the file is unwritable (read-only checkout, weird perms), log a warning and continue rather than failing the whole stage.
Worth doing in the corresponding detach path too: when the user runs gbrain sources detach (or the orchestrator removes a stale pin), the .gitignore entry should stay (harmless) — but if you ever switch the orchestrator to also clean up the gitignore line on detach, that's symmetric.
Why this matters more for Conductor users
Single-worktree users mostly don't notice — the pin tracks the only checkout, so even if it gets committed, it points at the right source. Conductor users who run /sync-gbrain in two sibling worktrees of the same repo+branch get different path-hash source IDs (correct, per v1.29.0.0) but if either pin gets committed, git pull in the sibling overwrites the local pin and routes queries to the wrong worktree's source. This is exactly the failure mode v1.29.0.0 claimed to prevent.
Environment
- gstack v1.29.0.0 (master @ 0660547)
- gbrain v0.30.1
- OS: Ubuntu 22.04
- Setup: standalone repo (not Conductor), but the failure mode applies to Conductor users specifically
Hit while running /sync-gbrain on a single-worktree TMASearcher repo immediately after the gstack v1.28→v1.29 upgrade. Manually appended .gbrain-source to the repo's .gitignore to work around.
Summary
gstack-gbrain-syncwrites.gbrain-sourceto the consuming repo's root aftergbrain sources attach <id>succeeds, but it doesn't add.gbrain-sourceto that repo's.gitignore. The v1.29.0.0 changelog explicitly promised this:The change actually shipped only added
.gbrain-sourceto gstack's own.gitignore, not the consumer repo's. Without the consumer-side gitignore entry, the pin file gets committed and breaks the per-worktree promise: Conductor sibling worktrees of the same repo + branch step on each other's pin every time anyone commits.Repro
Then if a teammate (or sibling Conductor worktree) commits unrelated work with
git add -Aor includes the pin in a commit by accident, the pin propagates to other branches/worktrees and silently overrides their own pin on next checkout.Suggested fix
In
bin/gstack-gbrain-sync.ts:runCodeImport, right aftergbrain sources attach <id>succeeds (around line 336-351 per the v1.29.0.0 path-hash work), append.gbrain-sourceto<root>/.gitignoreif not already present:Wrap in try/catch — if the file is unwritable (read-only checkout, weird perms), log a warning and continue rather than failing the whole stage.
Worth doing in the corresponding
detachpath too: when the user runsgbrain sources detach(or the orchestrator removes a stale pin), the.gitignoreentry should stay (harmless) — but if you ever switch the orchestrator to also clean up the gitignore line on detach, that's symmetric.Why this matters more for Conductor users
Single-worktree users mostly don't notice — the pin tracks the only checkout, so even if it gets committed, it points at the right source. Conductor users who run
/sync-gbrainin two sibling worktrees of the same repo+branch get different path-hash source IDs (correct, per v1.29.0.0) but if either pin gets committed,git pullin the sibling overwrites the local pin and routes queries to the wrong worktree's source. This is exactly the failure mode v1.29.0.0 claimed to prevent.Environment
Hit while running
/sync-gbrainon a single-worktree TMASearcher repo immediately after the gstack v1.28→v1.29 upgrade. Manually appended.gbrain-sourceto the repo's.gitignoreto work around.