Skip to content

[Low] performFullSync ignores dry_run flag on first/full sync #140

@Hybirdss

Description

@Hybirdss

Summary

performFullSync (src/commands/sync.ts:281-302) does not honor
opts.dryRun. Calling sync_brain with dry_run: true on a fresh repo
(first sync / full sync path) still writes pages to the brain — the flag
is never passed into the runImport call chain. Incremental syncs honor
the flag correctly (src/commands/sync.ts:137-156), so this is a
first-sync / full-sync-only gap.

// src/commands/sync.ts:281-302 — performFullSync ignores opts.dryRun
async function performFullSync(
  engine: BrainEngine,
  repoPath: string,
  headCommit: string,
  opts: SyncOpts,
): Promise<SyncResult> {
  console.log(`Running full import of ${repoPath}...`);
  const { runImport } = await import('./import.ts');
  const importArgs = [repoPath];
  if (opts.noEmbed) importArgs.push('--no-embed');
  await runImport(engine, importArgs);       // ← opts.dryRun never checked, never passed
  await engine.setConfig('sync.last_commit', headCommit);
  // ...
}

Compare to the incremental sync path (sync.ts:137-156), which short-circuits
cleanly when dry_run is set:

if (opts.dryRun) {
  console.log(`Sync dry run: ${lastCommit.slice(0, 8)}..${headCommit.slice(0, 8)}`);
  // ... prints what would change, returns status: 'dry_run' without any writes
}

Location

src/commands/sync.ts:281-302   (performFullSync — flag ignored)
src/commands/sync.ts:137-156   (incremental path — flag honored, for comparison)

Reproduction

Verified against master HEAD b7e3005 (v0.10.1).

mkdir /tmp/test-repo && cd /tmp/test-repo
git init -q
echo "# test page" > page.md
git add -A && git commit -qm init

# Fresh repo → full sync path → dry_run ignored
gbrain call sync_brain '{"repo":"/tmp/test-repo","dry_run":true,"no_pull":true,"no_embed":true}'
# → "Running full import of /tmp/test-repo... 1 pages imported"

# Second call confirms writes persisted: a real dry run would have left the brain untouched,
# so this call would also report "first_sync" again. Instead it returns "up_to_date":
gbrain call sync_brain '{"repo":"/tmp/test-repo","dry_run":true,"no_pull":true,"no_embed":true}'
# → { "status": "up_to_date" }

The first call wrote to the brain despite dry_run: true. The second call sees the
persisted checkpoint and reports up_to_date, confirming the first call was not a
dry run.

Suggested fix

Pass opts.dryRun into the runImport call chain at sync.ts:281-302 and
short-circuit persistence in the chunker / DB-write layer when set.

Minimal-scope alternative — mirror the incremental short-circuit at the top of
performFullSync:

if (opts.dryRun) {
  const { isSyncable } = await import('../core/sync.ts');
  // enumerate candidate files without writing, return { status: 'dry_run', ... }
  return { status: 'dry_run', fromCommit: null, toCommit: headCommit, /* ... */ };
}

Metadata

Severity Low
Category Advisory flag bypass (correctness)
Confidence High (runtime-verified)
Affected version v0.10.1 (master HEAD b7e3005)
Reachable via MCP sync_brain, CLI gbrain call sync_brain

[2026-04-16 updated] This issue originally combined the dry_run bug above
with a broader framing about sync_brain accepting arbitrary caller-supplied
git repo paths. On reflection that framing was too broad — repo is a
documented parameter and "the MCP caller supplies a path to sync" is the
tool's intended behavior, not an authorization bypass. I've narrowed this
issue to just the dry_run bypass, which is an unambiguous correctness bug.
Apologies for the initial framing breadth.

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