Skip to content

Commit 8ea197c

Browse files
committed
fix(bun-compile): replace build-time patches with source-level VFS checks
Move openBoundaryFileSync VFS fast path and config validation $bunfs issue filtering from fragile build-time regex patches to proper source code with isBunfsPath/isBunCompiledBinary checks. This eliminates two of the most failure-prone patch points that broke on every upstream version bump.
1 parent b93b6a0 commit 8ea197c

File tree

3 files changed

+33
-48
lines changed

3 files changed

+33
-48
lines changed

scripts/build-bun-compile.ts

Lines changed: 3 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -292,52 +292,9 @@ export async function readPackageName(_root) { return ${JSON.stringify(ctx.pkgJs
292292
contents: patchManifestTs(readFileSync(args.path, "utf-8")),
293293
loader: "ts",
294294
}));
295-
// Patch config/validation.ts: filter $bunfs manifest issues inside
296-
// validateConfigObjectWithPluginsBase itself. Bun inlines callers ~32
297-
// times, so patching callers in io.ts only covers 2. Patch the source.
298-
build.onLoad({ filter: /config[/\\]validation\.ts$/ }, (args) => {
299-
let src = readFileSync(args.path, "utf-8");
300-
// Target the final return in validateConfigObjectWithPluginsBase (after plugin loop)
301-
// which is `if (issues.length > 0) {\n return { ok: false, issues, warnings };\n }\n\n return { ok: true, config, warnings };\n}`
302-
src = src.replaceAll(
303-
/if \(issues\.length > 0\) \{\s*return \{ ok: false, issues, warnings \};\s*\}\s*\n\s*return \{ ok: true, config, warnings \};\s*\}/g,
304-
(match) => {
305-
return [
306-
`{`,
307-
` const __filtered = issues.filter((iss: any) => !(typeof iss.message === "string" && iss.message.includes("manifest not found") && iss.message.includes("$bunfs")));`,
308-
` if (__filtered.length > 0) return { ok: false as const, issues: __filtered, warnings };`,
309-
`}`,
310-
`return { ok: true as const, config, warnings };`,
311-
`}`,
312-
].join("\n");
313-
},
314-
);
315-
return { contents: src, loader: "ts" };
316-
});
317-
// Patch openBoundaryFileSync to handle $bunfs virtual paths.
318-
// Boundary validation (symlinks, hardlinks, TOCTOU identity) doesn't
319-
// apply to virtual filesystem paths and fails because VFS openSync
320-
// creates temp files whose fstat doesn't match the VFS lstat.
321-
build.onLoad({ filter: /infra[/\\]boundary-file-read\.ts$/ }, (args) => {
322-
let src = readFileSync(args.path, "utf-8");
323-
src = src.replace(
324-
/export function openBoundaryFileSync\(params: OpenBoundaryFileSyncParams\): BoundaryFileOpenResult \{/,
325-
[
326-
`export function openBoundaryFileSync(params: OpenBoundaryFileSyncParams): BoundaryFileOpenResult {`,
327-
` if (typeof params.absolutePath === "string" && (params.absolutePath.includes("$bunfs") || params.absolutePath.includes("B:\\\\~BUN"))) {`,
328-
` const ioFs = params.ioFs ?? fs;`,
329-
` try {`,
330-
` const fd = ioFs.openSync(params.absolutePath, ioFs.constants.O_RDONLY);`,
331-
` const stat = ioFs.fstatSync(fd);`,
332-
` return { ok: true, path: params.absolutePath, fd, stat, rootRealPath: params.rootPath };`,
333-
` } catch {`,
334-
` return { ok: false, reason: "io" };`,
335-
` }`,
336-
` }`,
337-
].join("\n"),
338-
);
339-
return { contents: src, loader: "ts" };
340-
});
295+
// NOTE: openBoundaryFileSync VFS fast path and config validation $bunfs
296+
// issue filtering are now handled in source code (boundary-file-read.ts
297+
// and validation.ts) instead of build-time patches.
341298
// NOTE: playwright-core is bundled (not external) so Bun handles it.
342299
// Patch workspace-templates to prefer VFS-embedded templates directory
343300
build.onLoad({ filter: /agents[/\\]workspace-templates\.ts$/ }, (args) => {

src/config/validation.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from "node:path";
2+
import { isBunCompiledBinary } from "../daemon/runtime-binary.js";
23
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
34
import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/registry.js";
45
import { withBundledPluginAllowlistCompat } from "../plugins/bundled-compat.js";
@@ -668,8 +669,15 @@ function validateConfigObjectWithPluginsBase(
668669
}
669670
}
670671

671-
if (issues.length > 0) {
672-
return { ok: false, issues, warnings };
672+
// In Bun compiled binaries, VFS-embedded extensions that lack a manifest
673+
// file produce spurious "plugin manifest not found: /$bunfs/..." issues.
674+
// Filter them out so they don't block config validation.
675+
const finalIssues = isBunCompiledBinary()
676+
? issues.filter((iss) => !(iss.message.includes("manifest not found") && iss.message.includes("$bunfs")))
677+
: issues;
678+
679+
if (finalIssues.length > 0) {
680+
return { ok: false, issues: finalIssues, warnings };
673681
}
674682

675683
return { ok: true, config, warnings };

src/infra/boundary-file-read.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ import {
1212
type SafeOpenSyncFailureReason,
1313
} from "./safe-open-sync.js";
1414

15+
// Bun compiled binary virtual filesystem paths bypass boundary validation
16+
// because symlink/hardlink/TOCTOU checks don't apply to VFS.
17+
const BUNFS_PREFIX_UNIX = "/$bunfs/";
18+
const BUNFS_PREFIX_WIN = "B:\\~BUN\\";
19+
function isBunfsPath(p: string): boolean {
20+
return p.startsWith(BUNFS_PREFIX_UNIX) || p.startsWith(BUNFS_PREFIX_WIN);
21+
}
22+
1523
type BoundaryReadFs = Pick<
1624
typeof fs,
1725
| "closeSync"
@@ -67,6 +75,18 @@ export function canUseBoundaryFileOpen(ioFs: typeof fs): boolean {
6775
}
6876

6977
export function openBoundaryFileSync(params: OpenBoundaryFileSyncParams): BoundaryFileOpenResult {
78+
// Fast path for Bun compiled binary VFS paths — boundary validation
79+
// (symlinks, hardlinks, TOCTOU identity) doesn't apply to virtual FS.
80+
if (isBunfsPath(params.absolutePath)) {
81+
const ioFs = params.ioFs ?? fs;
82+
try {
83+
const fd = ioFs.openSync(params.absolutePath, ioFs.constants.O_RDONLY);
84+
const stat = ioFs.fstatSync(fd);
85+
return { ok: true, path: params.absolutePath, fd, stat, rootRealPath: params.rootPath };
86+
} catch {
87+
return { ok: false, reason: "io" };
88+
}
89+
}
7090
const ioFs = params.ioFs ?? fs;
7191
const resolved = resolveBoundaryFilePathGeneric({
7292
absolutePath: params.absolutePath,

0 commit comments

Comments
 (0)