Summary
./container/build.sh fails when using Apple Container (Apple's native container runtime on macOS) due to several interconnected issues in how Apple Container's build context scanner works and how @anthropic-ai/claude-agent-sdk and zod@4.x package their dependencies.
Environment
- macOS (Apple Silicon)
- Apple Container runtime (
container CLI) instead of Docker
@anthropic-ai/claude-agent-sdk@0.2.92
- NanoClaw v1.2.49
Root Cause: Apple Container's Build Context Scanner
Apple Container has an aggressive build context scanner that runs before .dockerignore is applied. It:
- Scans ALL JS files in the build context for npm bare specifiers (both
import and require() patterns, including those inside string literals)
- Tries to verify referenced npm package files exist on the host (in
node_modules/)
- Also reads npm package files from the host's source directory (not just the build context), so files excluded via rsync/dockerignore can still be reached
What Fails and Why
Issue 1: @anthropic-ai/claude-agent-sdk uses Bun's virtual filesystem
The SDK uses Bun's bundler internally. Files referenced in its package (e.g., resvg.wasm, embed.js, audio-capture.node, bun.lock) don't exist as normal disk files — they're Bun virtual FS entries. Apple Container's scanner finds these references and fails with "file not found".
Fix applied (in this fork): Pre-bundle agent-runner/dist/index.js using esbuild into a single bundle/index.js with --bundle --minify. The minified bundle has no bare npm specifiers, so the scanner has nothing to follow. The container/build.sh rsyncs only agent-runner/bundle/ to the temp build context.
Issue 2: ajv code-generation strings trigger the scanner
After esbuild bundling, the bundle still contains strings like 'require("ajv/dist/runtime/uri").default' — these are string literals set as .code properties in ajv's runtime modules, used for debug/source output by ajv's code generator. They are never executed as actual require() calls. However, Apple Container's scanner treats them as real requires.
Fix applied: Post-process the bundle with sed to replace require("ajv/dist/runtime/...") strings with ajv_bundled — safe because these strings are only used in code generation for debugging, not at runtime.
Issue 3: Apple Container reads HOST's dist/index.js
Even with the temp build context containing only agent-runner/bundle/index.js, Apple Container still reads the host's dist/index.js (in the original source directory). That file contains:
const mcpServerPath = path.join(__dirname, 'ipc-mcp-stdio.js');
Apple Container resolves this and looks for agent-runner/dist/ipc-mcp-stdio.js in the build context (where it doesn't exist because dist/ is excluded). Error:
The file "ipc-mcp-stdio.js" couldn't be opened because there is no such file.
NSFilePath=/tmp/nanoclaw-build-XXXX/agent-runner/dist/ipc-mcp-stdio.js
Not yet fixed. Possible fix: delete dist/ from host before running container build, combined with also bundling ipc-mcp-stdio.js.
Issue 4: ipc-mcp-stdio.js cannot be bundled with esbuild
ipc-mcp-stdio.ts imports from @modelcontextprotocol/sdk which in turn imports zod/v3 and zod/v4-mini subpaths. These subpaths are declared in zod@4.3.6's package.json exports but the actual files don't exist on disk:
node_modules/zod/v3/ → does not exist
node_modules/zod/v4-mini/ → does not exist
Additionally, zod@4.3.6's own index.js imports ./v4/classic/external.js which also doesn't exist, making zod@4.x completely non-functional in Node.js/esbuild context:
node:internal/modules/esm/resolve: Cannot find module
'zod/v4/classic/external.js'
Root cause: zod@4.3.6 appears to use Bun's virtual filesystem (same as claude-agent-sdk), making it incompatible with standard Node.js module loading.
Not yet fixed. Using zod@^3.25 instead would work, but @anthropic-ai/claude-agent-sdk@0.2.92 declares zod@"^4.0.0" as a peer dependency, causing npm install to fail with a conflict.
Full Error Progression
| Attempt |
Error |
Root Cause |
| Original (no changes) |
resvg.wasm, embed.js not found |
claude-agent-sdk Bun virtual FS |
| After esbuild bundle |
agent-runner/dist/index.d.ts not in build context |
Apple Container reads host package.json with "main": "dist/index.js", checks for .d.ts |
After main: "bundle/index.js" |
agent-runner/node_modules/.package-lock.json not in build context |
ajv strings in bundle trigger package resolution |
| After sed fix on ajv strings |
agent-runner/dist/ipc-mcp-stdio.js not in build context |
Apple Container reads host dist/index.js |
| After attempting ipc-mcp-stdio.js bundle |
esbuild fails: zod/v4/classic/external.js not found |
zod@4.3.6 is Bun-only package |
Proposed Complete Fix
-
Delete dist/ from host before container build to prevent Apple Container from reading it:
# In build.sh, after npm run build:
rm -rf "${SCRIPT_DIR}/agent-runner/dist"
-
Bundle ipc-mcp-stdio.js with --legacy-peer-deps to use zod@^3.25:
- Use
npm install --legacy-peer-deps in build.sh (peer dep is advisory since claude-agent-sdk bundles its own zod)
- Add
dist/ipc-mcp-stdio.js as second esbuild entry point with --outdir=bundle
- Apply the sed fix to all bundle files
-
Report upstream: Apple Container's behavior of reading from the host's source directory (not just the build context) is unexpected and inconsistent with Docker's behavior. This should be reported to Apple.
Related
Summary
./container/build.shfails when using Apple Container (Apple's native container runtime on macOS) due to several interconnected issues in how Apple Container's build context scanner works and how@anthropic-ai/claude-agent-sdkandzod@4.xpackage their dependencies.Environment
containerCLI) instead of Docker@anthropic-ai/claude-agent-sdk@0.2.92Root Cause: Apple Container's Build Context Scanner
Apple Container has an aggressive build context scanner that runs before
.dockerignoreis applied. It:importandrequire()patterns, including those inside string literals)node_modules/)What Fails and Why
Issue 1:
@anthropic-ai/claude-agent-sdkuses Bun's virtual filesystemThe SDK uses Bun's bundler internally. Files referenced in its package (e.g.,
resvg.wasm,embed.js,audio-capture.node,bun.lock) don't exist as normal disk files — they're Bun virtual FS entries. Apple Container's scanner finds these references and fails with "file not found".Fix applied (in this fork): Pre-bundle
agent-runner/dist/index.jsusing esbuild into a singlebundle/index.jswith--bundle --minify. The minified bundle has no bare npm specifiers, so the scanner has nothing to follow. Thecontainer/build.shrsyncs onlyagent-runner/bundle/to the temp build context.Issue 2: ajv code-generation strings trigger the scanner
After esbuild bundling, the bundle still contains strings like
'require("ajv/dist/runtime/uri").default'— these are string literals set as.codeproperties in ajv's runtime modules, used for debug/source output by ajv's code generator. They are never executed as actualrequire()calls. However, Apple Container's scanner treats them as real requires.Fix applied: Post-process the bundle with
sedto replacerequire("ajv/dist/runtime/...")strings withajv_bundled— safe because these strings are only used in code generation for debugging, not at runtime.Issue 3: Apple Container reads HOST's
dist/index.jsEven with the temp build context containing only
agent-runner/bundle/index.js, Apple Container still reads the host'sdist/index.js(in the original source directory). That file contains:Apple Container resolves this and looks for
agent-runner/dist/ipc-mcp-stdio.jsin the build context (where it doesn't exist becausedist/is excluded). Error:Not yet fixed. Possible fix: delete
dist/from host before runningcontainer build, combined with also bundlingipc-mcp-stdio.js.Issue 4:
ipc-mcp-stdio.jscannot be bundled with esbuildipc-mcp-stdio.tsimports from@modelcontextprotocol/sdkwhich in turn importszod/v3andzod/v4-minisubpaths. These subpaths are declared inzod@4.3.6'spackage.jsonexports but the actual files don't exist on disk:Additionally,
zod@4.3.6's ownindex.jsimports./v4/classic/external.jswhich also doesn't exist, makingzod@4.xcompletely non-functional in Node.js/esbuild context:Root cause:
zod@4.3.6appears to use Bun's virtual filesystem (same asclaude-agent-sdk), making it incompatible with standard Node.js module loading.Not yet fixed. Using
zod@^3.25instead would work, but@anthropic-ai/claude-agent-sdk@0.2.92declareszod@"^4.0.0"as a peer dependency, causingnpm installto fail with a conflict.Full Error Progression
resvg.wasm,embed.jsnot foundagent-runner/dist/index.d.tsnot in build contextpackage.jsonwith"main": "dist/index.js", checks for.d.tsmain: "bundle/index.js"agent-runner/node_modules/.package-lock.jsonnot in build contextagent-runner/dist/ipc-mcp-stdio.jsnot in build contextdist/index.jszod/v4/classic/external.jsnot foundProposed Complete Fix
Delete
dist/from host beforecontainer buildto prevent Apple Container from reading it:Bundle
ipc-mcp-stdio.jswith--legacy-peer-depsto usezod@^3.25:npm install --legacy-peer-depsin build.sh (peer dep is advisory since claude-agent-sdk bundles its own zod)dist/ipc-mcp-stdio.jsas second esbuild entry point with--outdir=bundleReport upstream: Apple Container's behavior of reading from the host's source directory (not just the build context) is unexpected and inconsistent with Docker's behavior. This should be reported to Apple.
Related
COPYinstructions by scanning the SOURCE package on the host, not just the files in the build context passed tocontainer buildzod@4.xBun-only packaging: https://github.com/colinhacks/zod