Skip to content

Git worktrees misclassified as submodules — breaks code-sync + gitignore management on Conductor workflows #889

@agile-operators

Description

@agile-operators

Summary

gbrain's submodule heuristic checks only whether .git is a file vs directory. Git worktrees also have .git as a file (the gitdir pointer is the worktree mechanism), so worktrees are incorrectly treated as submodules. This breaks code-sync silently and produces a misleading warning.

Affects every Conductor user (Anthropic's parallel-agent workspace tool), which builds workspaces as git worktrees of a single canonical repo.

Reproduce

git clone https://github.com/<you>/<some-repo>.git ~/code/myrepo
cd ~/code/myrepo
git worktree add /tmp/wt-feature -b feature/xyz
cd /tmp/wt-feature

gbrain sources add gstack-code-wt --path "$(pwd)" --federated
gbrain sync --strategy code --source gstack-code-wt
gbrain sources list | grep gstack-code-wt   # page_count=0

Observed (on v0.32.0, also v0.27.0)

Already up to date.
Note: skipping .gitignore management — /tmp/wt-feature is a git submodule. Add db_only directories to your parent repo's .gitignore manually.
  • page_count for the worktree source stays 0.
  • gbrain code-def / code-refs / code-callers return nothing for the worktree.
  • The warning points at the wrong cause (submodule), so users don't know to investigate worktrees.

Expected

Worktrees should be code-indexed like any normal repo. The submodule warning should fire only for actual submodules.

Root cause

src/commands/sync.ts:1149-1164:

const dotGit = join(repoPath, '.git');
if (existsSync(dotGit)) {
  try {
    if (statSync(dotGit).isFile()) {
      console.warn(`Note: skipping .gitignore management — ${repoPath} is a git submodule. ...`);
      return;
    }
  } catch { ... }
}

isFile() is true for both:

  • Submodule .git: gitdir: ../.git/modules/<name> (relative, contains /modules/)
  • Worktree .git: gitdir: /path/to/main/.git/worktrees/<name> (absolute, contains /worktrees/)

Reading the file and inspecting the gitdir path distinguishes them.

Suggested fix shape

if (st.isFile()) {
  const contents = readFileSync(dotGit, 'utf8').trim();
  const gitdir = contents.match(/^gitdir:\s*(.+)$/m)?.[1] ?? '';
  if (/\/modules\//.test(gitdir)) {
    console.warn(`Note: skipping .gitignore management — ${repoPath} is a git submodule. ...`);
    return;
  }
  // worktree (/worktrees/) or unknown — fall through, treat as normal repo
}

Secondary observation

After upgrading 0.27.0 → 0.32.0, gbrain sync --full --strategy code --source <id> against a worktree starts walking files (209 found), but every file errors with:

column "source_id" of relation "ingest_log" does not exist

Re-running gbrain apply-migrations --yes leaves migrations v0.28.0 PARTIAL and v0.29.1 in status=failed with getaddrinfo ENOTFOUND (target host unknown — DNS resolves fine for aws-1-us-east-1.pooler.supabase.com and api.openai.com). Doctor reports MINIONS HALF-INSTALLED. May share a root cause with the worktree detection; flagging in case. Happy to file separately if preferred.

Environment

  • gbrain v0.27.0 (originally) and v0.32.0 (after upgrade) — both reproduce
  • macOS 26.4.1, bash
  • Brain engine: postgres (Supabase pooler)
  • Worktree pattern: Anthropic Conductor (each workspace = git worktree add from a canonical clone)

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