Skip to content

/sync-gbrain doesn't add .gbrain-source to consumer repo's .gitignore (v1.29.0.0 changelog promised it) #1384

@axe08

Description

@axe08

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions