Skip to content

Add single-file executable using bun#332

Merged
benbrandt merged 8 commits intozed-industries:mainfrom
SteffenDE:sd-bun-single-file-exec
Feb 20, 2026
Merged

Add single-file executable using bun#332
benbrandt merged 8 commits intozed-industries:mainfrom
SteffenDE:sd-bun-single-file-exec

Conversation

@SteffenDE
Copy link
Contributor

This adds a single-file executable using bun. The trick is to use a separate flag for ACP --acp and spawn the regular cli.js bundled by the Agent SDK if no flags are passed. This was, we can pass ourselves as pathToClaudeCodeExecutable.

@cla-bot cla-bot bot added the cla-signed label Feb 19, 2026
@SteffenDE
Copy link
Contributor Author

@josevalim
Copy link
Contributor

@benbrandt as we discussed in the past, this is the PR for precompiled claude-code-acp so we don't need to depend on npx/bunx :)

Copy link
Member

@benbrandt benbrandt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall seems good! a few questiosn

@SteffenDE SteffenDE force-pushed the sd-bun-single-file-exec branch from fcfc8bd to 63175ae Compare February 20, 2026 10:50
Copy link
Member

@benbrandt benbrandt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! two more questions, otherwise we can just go with it

@benbrandt benbrandt merged commit 101f09d into zed-industries:main Feb 20, 2026
2 checks passed
@SteffenDE SteffenDE deleted the sd-bun-single-file-exec branch February 20, 2026 11:23
@lixiaoyan
Copy link

zed-industries/zed#50376

This change appears to cause an issue where external tools cannot be correctly located.

@lixiaoyan

This comment was marked as outdated.

@josevalim
Copy link
Contributor

josevalim commented Feb 28, 2026

@lixiaoyan so perhaps they fixed the issue for cli.js but not for rg?

Perhaps a fix is to set USE_BUILTIN_RIPGREP=0 and then we change the PATH to point to the rg inside claude but that seems to be a bug on the SDK, so I think it has to be reported to the SDK. It is the same as above, except it happens for rg.

@lixiaoyan
Copy link

lixiaoyan commented Feb 28, 2026

I've confirmed that after switching the agent invocation method in Zed to npx, the issue mentioned above no longer occurs. This confirms that this PR is indeed the root cause of the problem. I consulted Claude on this issue, and it suggested that we should use the --embed flag during compilation to address the related problem. I haven't tested whether this actually works yet, but I wanted to flag it first.

@josevalim
Copy link
Contributor

josevalim commented Feb 28, 2026

Apparently the SDK ships with this:

vendor
└── ripgrep
    ├── arm64-darwin
    │   ├── rg
    │   └── ripgrep.node
    ├── arm64-linux
    │   ├── rg
    │   └── ripgrep.node
    ├── arm64-win32
    │   ├── rg.exe
    │   └── ripgrep.node
    ├── COPYING
    ├── x64-darwin
    │   ├── rg
    │   └── ripgrep.node
    ├── x64-linux
    │   ├── rg
    │   └── ripgrep.node
    └── x64-win32
        ├── rg.exe
        └── ripgrep.node

When using the SDK (as a node module), it does this:

  let q = xr.resolve(ETK, "vendor", "ripgrep")
  let ETK = dirname(fileURLToPath(import.meta.url))

But now, because of the single-file Bun, the import.meta.url resolves to /$bunfs/root/, which makes it fail (the error you reported).

It seems the SDK code accounts for Bun.embeddedFiles but I believe it is only for shipping claude itself as an executable. In this case, it embeds ripgrep.node inside the __BUN Mach-O segment (you can verify it with otool on macOS) then, at runtime, Bun loads and calls it as an in-process ripgrepMain function. But all of this code is effectively deadcode from the point of view of the SDK.

I think redoing the native embedding for our Bun executable will be a lot of work, so the best option is to indeed:

  1. Embed vendor/ripgrep/{process.arch}-{process.platform}/rg from the SDK inside our Bun executable (but make sure we are not embedding rg twice: one explicitly and another from the npm package)
  2. When we start the executable, we unpack the executable into a tmp directory
  3. Then we add the temp directory to $PATH and set USE_BUILTIN_RIPGREP=0

However, we would need to do this for every executable they add. For example, there are also some tree-sitter and wasm files at the root of the SDK. I asked Claude to check how they are required in the SDK and it returned with this:

  - resvg.wasm → new URL("resvg.wasm", import.meta.url)
  - tree-sitter.wasm → new URL("tree-sitter.wasm", import.meta.url)
  - tree-sitter-bash.wasm → resolved relative to package dir

So all of those will likely fail to load from our executable, if they are used at any moment (it is hard to say when).

Which means are our two remaining options are:

  1. Ship Claude Code executable itself with our single-file executable (it will be 200MB+)
  2. Give up on this and, if we want to provide a better UX, Zed/Tidewave can automatically install bun for the user and call bun x claude-agents-sdk@...

@benbrandt
Copy link
Member

Thanks all. I'll remove the binary distribution from the registry for now while we figure this out

@SteffenDE
Copy link
Contributor Author

SteffenDE commented Feb 28, 2026

Hrm that's a bummer. I agree that we should probably drop the single file executables, as working around those issues would probably only create more headaches in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants