Skip to content

harness-claude-code: readBridgeAsset probes ../bridge/, which never exists in a published install, so Turbopack fails the build #19982

Description

@brightiron

Description

Any Next.js 16 app that imports @ai-sdk/harness-claude-code into server code fails next build. Turbopack is the default build bundler in Next 16, so this is the default path, not an opt-in one.

./node_modules/@ai-sdk/harness-claude-code/dist/index.js:74:5
Error: Module not found: Can't resolve '../bridge/' <dynamic>

  72 |   const candidates = [
  73 |     new URL(`./bridge/${name}`, import.meta.url),
> 74 |     new URL(`../bridge/${name}`, import.meta.url)
     |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  75 |   ];

readBridgeAsset in dist/index.js builds two candidate URLs. Resolved from dist/index.js, the second one points at <pkg>/bridge/. The published tarball only ships dist/bridge/:

$ ls node_modules/@ai-sdk/harness-claude-code
CHANGELOG.md  LICENSE  README.md  dist  package.json  src
$ ls node_modules/@ai-sdk/harness-claude-code/dist
bridge  index.d.ts  index.js  index.js.map

So the second candidate can never resolve in an installed package. At runtime that is harmless, since the first candidate returns and the second only runs on a caught ENOENT. But Turbopack statically analyzes both dynamic paths and treats the unresolvable one as a hard build error.

It is bundler-specific, which is probably why it has gone unreported. Same app, same package, only the bundler changed:

bundler result
Turbopack (Next 16 default) Module not found: Can't resolve '../bridge/' <dynamic>
webpack (next build --webpack) ✓ Compiled successfully in 2.2s, no warning

File tracing is not the problem. @vercel/nft 1.11.0 traces all four bridge assets (package.json, pnpm-lock.yaml, pnpm-workspace.yaml, index.mjs) from a plain import, so the template literal itself is fine. It is only the candidate that points outside the shipped layout.

Not a regression. I pulled tarballs for 1.0.1, 1.0.10, 1.0.30, 1.0.50, 1.0.65, 1.0.70, 1.0.73, 1.0.76, 1.0.79, 1.0.82, 1.0.85, 1.0.89 and 1.0.97, and ../bridge/ is in every one. This looks like an omission from the start rather than something that broke recently.

Workaround

Adding the package to serverExternalPackages in next.config.ts avoids it, which is what we have been doing. That works, but it forces the whole dependency chain external for a reason that has nothing to do with wanting it external.

Possible fixes

  1. Drop the ../bridge/ candidate. It cannot resolve in any published install, so it is dead in the only layout users get.
  2. If it exists for a source or monorepo layout, gate it so only the shipped path survives into dist, or derive both candidates from one base that always resolves.
  3. Separately, consider exposing the assets through an exports subpath, for example "./bridge/*": "./dist/bridge/*". Today exports is only "." and "./package.json". Consumers who need the bridge lockfile have to require.resolve the package.json and then path.join into dist/bridge, which reaches into internals that are not part of the public surface. We do this to hash the bridge lockfile so a sandbox snapshot invalidates when the pinned Claude Code version changes.

What I did not check

Whether other bundlers (Vite, Rollup, esbuild) reject it the same way. I only tested Turbopack and webpack through Next. Also only tested on Node 23.5.0. It is a build-time static analysis error, so the Node version should not matter, but I did not verify that on another version.

Reproduction

Four files, no next.config:

mkdir repro && cd repro
npm init -y
npm install next@latest react@latest react-dom@latest @ai-sdk/harness-claude-code@latest
mkdir -p app/api/probe

app/api/probe/route.js:

import { createClaudeCode } from '@ai-sdk/harness-claude-code';

export const dynamic = 'force-dynamic';

export async function GET() {
  return Response.json({ ok: typeof createClaudeCode });
}

app/layout.js:

export default function RootLayout({ children }) {
  return <html lang="en"><body>{children}</body></html>;
}

app/page.js:

export default function Page() {
  return <main>repro</main>;
}

Then:

npx next build              # fails, Module not found: '../bridge/'
npx next build --webpack    # passes

AI SDK Version

package version
@ai-sdk/harness-claude-code 1.0.97
@ai-sdk/harness 1.0.93 (transitive)
next 16.3.3
react / react-dom 19.2.8
node 23.5.0
npm 10.9.2
OS macOS 26.6.1

Also reproduces on next@16.3.2 with @ai-sdk/harness-claude-code@1.0.89 under pnpm, so it is not specific to the flat npm layout or to the newest Next patch.

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions