Skip to content

Apple Container build fails: scanner reads HOST package files, Bun-bundled SDK & zod@4.x incompatible with esbuild #1659

@ergut

Description

@ergut

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:

  1. Scans ALL JS files in the build context for npm bare specifiers (both import and require() patterns, including those inside string literals)
  2. Tries to verify referenced npm package files exist on the host (in node_modules/)
  3. 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

  1. 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"
  2. 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
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions