Skip to content

Autopilot/dream sync fails with FK constraint error in multi-source brains #1078

@samvilian

Description

@samvilian

Bug Description

When running gbrain autopilot or gbrain dream in a multi-source brain (e.g. multiple sources registered via gbrain sources add), the sync phase fails with a foreign key constraint violation:

insert or update on table "pages" violates foreign key constraint "pages_source_id_fkey"

This causes all file imports in the cycle to fail silently, leaving the brain stale.

Root Cause

In src/core/cycle.ts, runPhaseSync() resolves the source ID by matching the autopilot's --repo path against sources.local_path:

// src/core/cycle.ts:558
const sourceId = await resolveSourceForDir(engine, brainDir);

resolveSourceForDir() does an exact match:

SELECT id FROM sources WHERE local_path = $1 LIMIT 1

When --repo /home/ubuntu/brain is passed but all sources have subdirectory paths like:

  • logseq/home/ubuntu/onedrive/OneSyncFiles/
  • wiki/home/ubuntu/brain/wiki/
  • obsidian/home/ubuntu/onedrive/my_obsidian/

No source matches, so sourceId resolves to undefined. The importFile() then falls back to the schema DEFAULT for source_id, which is 'default':

-- pages.source_id has default 'default'

But no row exists in sources with id = 'default', triggering the FK constraint violation on every insert.

Reproduction Steps

  1. Register multiple sources with different local_path values:

    gbrain sources add wiki --path /home/ubuntu/brain/wiki
    gbrain sources add obsidian --path /home/ubuntu/onedrive/my_obsidian
    gbrain sources add logseq --path /home/ubuntu/onedrive/OneSyncFiles
  2. Start autopilot with a parent directory that doesn't match any source:

    gbrain autopilot --repo /home/ubuntu/brain --interval 900
  3. Observe sync phase: all files fail with pages_source_id_fkey violation.

Environment

  • gbrain v0.35.1.0
  • Engine: postgres (Supabase)
  • Sources: 4 (logseq, wiki, obsidian, books)
  • OS: Linux arm64

Proposed Fix

Modify runPhaseSync() to enumerate all registered sources from the database and sync each one individually, rather than relying on a single brainDir match:

// Instead of resolving one source for brainDir:
const sourceId = await resolveSourceForDir(engine, brainDir);

// Enumerate all sources and sync each:
const sources = await engine.executeRaw<{ id: string; local_path: string }>(
  `SELECT id, local_path FROM sources WHERE local_path IS NOT NULL AND local_path != ''`,
);

for (const src of sources) {
  const result = await performSync(engine, {
    repoPath: src.local_path,
    sourceId: src.id,
    // ...
  });
}

This ensures each source is synced with its correct source_id, avoiding the FK constraint error. The fallback path is preserved for brains with no registered sources.

I can submit a PR with the full patch if this approach is acceptable.

Workaround

Manually sync each source individually:

gbrain sync --source logseq
gbrain sync --source wiki
gbrain sync --source obsidian

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