diff --git a/src/commands/import.ts b/src/commands/import.ts index 37d3d3dad..3270f9137 100644 --- a/src/commands/import.ts +++ b/src/commands/import.ts @@ -548,7 +548,10 @@ export function collectSyncableFiles(dir: string, opts: CollectOpts = {}): strin // Same set the legacy walkers honored, surfaced once at the top of // every iteration. if (entry.startsWith('.')) continue; - if (entry === 'node_modules' || entry === 'ops') continue; + // `venv` is the Python equivalent of `node_modules`: a vendored + // dependency tree (often thousands of files) that floods the slug + // namespace and triggers ON CONFLICT collisions on first full sync. + if (entry === 'node_modules' || entry === 'ops' || entry === 'venv') continue; const full = join(d, entry); let stat; diff --git a/src/core/sync.ts b/src/core/sync.ts index 6604343a6..1a753cb4c 100644 --- a/src/core/sync.ts +++ b/src/core/sync.ts @@ -241,6 +241,11 @@ function matchesAnyGlob(path: string, patterns?: string[]): boolean { */ const PRUNE_DIR_NAMES = new Set([ 'node_modules', + // Python venv: vendored dependency tree, the `node_modules` analogue. + // Like `node_modules` it lacks a leading dot so isSyncable's dot-prefix + // exclusion misses it; explicit entry keeps incremental sync consistent + // with the first-sync walker in commands/import.ts. + 'venv', '.raw', 'ops', ]);