From c8b3e1490416753b6f633e4cdbf9100e93aeb849 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Sun, 20 Sep 2026 05:44:11 -0400 Subject: [PATCH] fix: Close native scratch directory streams (#240) --- docs/remote-bridge/README.md | 5 ++++- packages/code/src/native-scratch.ts | 30 +++++++++++++++++------------ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/remote-bridge/README.md b/docs/remote-bridge/README.md index f66499b2..efe84b84 100644 --- a/docs/remote-bridge/README.md +++ b/docs/remote-bridge/README.md @@ -235,7 +235,10 @@ execution. - Remote bridge deployments use backend-specific BullMQ queues and serialize the expected backend on every new job, preventing Lambda or HTTP consumers from accepting attached-worker executions. -- Code API permits one active assignment per worker. +- Code API negotiates a bounded number of active workspace assignments per + worker. The lower API or worker slot ceiling wins, and assignments sharing + the same workspace isolation key remain serialized while independent + conversation worktrees may run concurrently. - Dynamic workers are fenced to their server-issued tenant before assignment. - Each assignment has an absolute deadline, generation, and random lease token. - Settlements with the wrong worker, generation, token, or expired deadline are diff --git a/packages/code/src/native-scratch.ts b/packages/code/src/native-scratch.ts index 0ecd330e..8394c1ae 100644 --- a/packages/code/src/native-scratch.ts +++ b/packages/code/src/native-scratch.ts @@ -151,19 +151,25 @@ export async function restoreScratchTraversal( if (directoryFd === undefined) continue; try { const directory = await opendir(descriptorPath(directoryFd)); - for await (const entry of directory) { - entriesInspected += 1; - if (entriesInspected > MAX_SCRATCH_ENTRIES) { - throw new Error('Native sandbox scratch cleanup exceeded its entry limit'); + try { + while (true) { + const entry = await directory.read(); + if (entry === null) break; + entriesInspected += 1; + if (entriesInspected > MAX_SCRATCH_ENTRIES) { + throw new Error('Native sandbox scratch cleanup exceeded its entry limit'); + } + if (!entry.isDirectory()) continue; + if (components.length >= MAX_SCRATCH_DEPTH) { + throw new Error('Native sandbox scratch cleanup exceeded its depth limit'); + } + if (pending.length >= MAX_SCRATCH_DIRECTORIES) { + throw new Error('Native sandbox scratch cleanup exceeded its directory limit'); + } + pending.push([...components, entry.name]); } - if (!entry.isDirectory()) continue; - if (components.length >= MAX_SCRATCH_DEPTH) { - throw new Error('Native sandbox scratch cleanup exceeded its depth limit'); - } - if (pending.length >= MAX_SCRATCH_DIRECTORIES) { - throw new Error('Native sandbox scratch cleanup exceeded its directory limit'); - } - pending.push([...components, entry.name]); + } finally { + await directory.close(); } } finally { if (directoryFd !== root.fd) closeDirectory(directoryFd);