Skip to content

cli: macOS V2 Bun build cannot load Parcel watcher binding #37686

Description

@kitlangton

Summary

The macOS arm64 V2 Bun executable cannot resolve its packaged @parcel/watcher-darwin-arm64 native binding. Directory watcher subscriptions log watcher backend not supported, preventing normal global/project config and plugin hot reload until the managed service is restarted.

Environment

  • opencode version: 0.0.0-next-15796
  • OS: macOS 25.5.0, Darwin 25.5.0 arm64
  • Terminal: WarpTerminal, TERM=xterm-256color, COLORTERM=truecolor
  • Shell: /bin/zsh
  • Install/channel: npm/Bun global install, next channel, Bun executable from @opencode-ai/cli-darwin-arm64
  • Active plugins: local global plugins under ~/.config/opencode/plugins/

Reproduction

  1. Create isolated directories:

    root="$(mktemp -d /tmp/opencode-watcher-repro.XXXXXX)"
    mkdir -p "$root/home/.config/opencode/plugins" "$root/project/.git"
    printf 'export default { id: "repro.plugin", setup: async () => {} }\n' \
      > "$root/home/.config/opencode/plugins/repro.ts"
  2. Start a non-service V2 server with only the temporary state:

    HOME="$root/home" \
    XDG_CONFIG_HOME="$root/home/.config" \
    XDG_DATA_HOME="$root/home/.local/share" \
    XDG_CACHE_HOME="$root/home/.cache" \
    OPENCODE_DISABLE_AUTOUPDATE=1 \
    OPENCODE_SERVER_PASSWORD=repro \
    opencode2 serve --hostname 127.0.0.1 --port 45999 --log-level all
  3. Boot a location:

    curl -u opencode:repro \
      -H "x-opencode-directory: $root/project" \
      http://127.0.0.1:45999/api/plugin
  4. Inspect $root/home/.local/share/opencode/log/opencode.log.

  5. Add or edit $root/home/.config/opencode/plugins/repro.ts and query /api/plugin again. The changed plugin generation is not loaded.

  6. As a control, start the same executable with OPENCODE_PARCEL_WATCHER_PATH pointing to a valid @parcel/watcher-darwin-arm64 package. The watcher starts with backend=fs-events.

Expected Behavior

Darwin arm64 should load the packaged Parcel native addon, start the fs-events directory backend, and emit config/plugin reload events when watched files are added or changed.

The log should include:

message="watcher started" type=directory backend=fs-events

Actual Behavior

Every directory subscription fails with:

level=ERROR message="watcher backend not supported" directory=<temporary path> platform=darwin role=server

Adding or editing a global plugin does not reload it. Restarting the managed service causes initial discovery to load the plugin, confirming that initial plugin loading works and watcher-driven reload does not.

Additional Context

origin/v2 explicitly supports Darwin:

if (process.platform === "darwin") return "fs-events"

The logged branch is:

if (!native || !backend) {
  return Effect.logError("watcher backend not supported", ...)
}

On Darwin, backend is "fs-events", so this message means native is undefined. The native loader currently suppresses the underlying exception:

const require = createRequire(import.meta.url)

const watcher = lazy(() => {
  try {
    const binding = require(
      process.env.OPENCODE_PARCEL_WATCHER_PATH ??
        `@parcel/watcher-${process.platform}-${process.arch}`,
    )
    return createWrapper(binding)
  } catch {
    return
  }
})

In the compiled Bun executable, createRequire(import.meta.url) resolves relative to /$bunfs/root. The platform npm package contains the executable but no external watcher.node, and the addon is not resolvable from the embedded filesystem.

The new Node SEA build already stages the addon as an asset, extracts it to a real filesystem path, and sets OPENCODE_PARCEL_WATCHER_PATH. The Bun build does not have an equivalent path.

Relevant origin/v2 source at 1f2de535aa7d13b966de667c2942d82fbde4dbf6:

  • packages/core/src/filesystem/watcher.ts:19-39: loader and Darwin backend selection
  • packages/core/src/filesystem/watcher.ts:86-115: directory subscription failure shuts down the update stream
  • packages/core/src/filesystem/watcher.ts:145-161: misleading unsupported-backend branch
  • packages/core/src/config.ts:302-350: config reload depends on watcher updates
  • packages/core/src/plugin/supervisor.ts:80-86: plugins are discovered from config directories
  • packages/core/src/plugin/supervisor.ts:240-266: plugin reload depends on config update events
  • packages/cli/script/build.ts:47-98: Bun build does not stage the Parcel native addon
  • packages/cli/script/node-assets.ts:31-56: Node build stages the addon correctly
  • packages/cli/vite.node.config.ts:139-165: Node SEA extracts the addon and sets its explicit path

Likely regression point: #36309 changed the shared watcher loader to createRequire(import.meta.url) while adding Node SEA asset support.

Related but not duplicate:

This report does not concern the separate managed-service restart connection interruption.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions