[BUG] `claude self-hosted-runner` and `claude plugin eval` start Claude Code processes without `CLAUDE_CODE_PROCESS_WRAPPER`

Status Open
Reported on v2.1.283
Maintainer reply None cached
Activity 1 comment · opened Sep 26, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

The corporate launcher documentation says that CLAUDE_CODE_PROCESS_WRAPPER "starts every process Claude Code launches from its own binary through your launcher". It lists the processes it covers and the exceptions, and every exception is a process something other than Claude Code starts (launchd/systemd, the user's terminal, the OS deep-link handler, tmux, the browser).

Two commands start Claude Code processes from its own binary without going through the launcher, and neither is on either list:

  • claude self-hosted-runner: the Claude Code process it starts for each session, the runner process it starts in the background, and the processes its setup and doctor subcommands start.
  • claude plugin eval: the Claude Code processes it starts.

In 2.1.283 these build their command from process.execPath (plus process.argv[1] outside a standalone executable) and spawn it directly. They never go through the common self-spawn path that applies the wrapper. Those chunks import the wrapper module only as a bare side-effect import and don't call it. Background sessions, the daemon and its workers, Remote Control sessions, agent-team panes, --resume relaunches and scheduled tasks all do go through the launcher.

For an organisation whose policy requires every Claude Code process to start through its launcher, these processes start unwrapped, with no warning. That matters most for the runner, whose sessions run in repositories it checks out.

Both commands were added after the wrapper: the wrapper in 2.1.208, the runner in 2.1.224, plugin eval in 2.1.269. So this looks like the wrapper not having been wired into them yet, rather than a deliberate exception. The runner does have its own --exec-path and command hook, but the documentation doesn't say they replace the wrapper.

What Should Happen?

Either:

  1. these commands start their Claude Code processes through the same self-spawn path as the other background processes, so CLAUDE_CODE_PROCESS_WRAPPER / processWrapper applies to them; or
  2. the corporate launcher documentation lists them under "Processes that start outside the launcher", with the runner's --exec-path/command hook named as the way to wrap runner sessions.

We'd much prefer 1: see the FreeBSD context below for why it matters to us.

Error Messages/Logs

None. The processes simply start without the launcher.

Steps to Reproduce

  1. Create a launcher that logs how it is called:

``sh
#!/bin/sh
printf '%s\n' "$*" >> /tmp/claude-launcher.log
exec "$@"
``

  1. export CLAUDE_CODE_PROCESS_WRAPPER=/path/to/that/launcher (chmod +x first).
  2. Control: claude --bg "say hi". /tmp/claude-launcher.log gets lines for the background service and its session, as in #81072.
  3. Run claude plugin eval on a plugin with an eval suite. No lines appear for the Claude Code processes it starts, although ps shows them.
  4. With a self-hosted environment, start claude self-hosted-runner and let it take a session. No line appears for the session's Claude Code process.

Claude Model

Not sure / Multiple models

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

2.1.283

Platform

Anthropic API

Operating System

Other

Terminal/Shell

Xterm

Additional Information

We're trying to give FreeBSD users a working Claude Code, packaged as misc/claude-code in the FreeBSD ports collection. There's no FreeBSD build, so we've been working around a few things; we'd be grateful for anything that makes this easier:

  • Linux binary under FreeBSD's Linux emulation. The port used to run your Linux binary under FreeBSD's Linux compatibility layer. From 2.1.269 it deadlocks shortly after starting, because of a FreeBSD kernel bug in the emulated epoll_pwait(2) signal mask (FreeBSD bug 298878). That's FreeBSD's bug, not yours, but it sent us looking for another way.
  • The bundle on FreeBSD's own Bun. We now extract the JavaScript from the official @anthropic-ai/claude-code-linux-x64 package and run it on Bun's official FreeBSD build (1.4.2). It works well. It does need small shims for the private Bun.ant APIs: without CellSegmenter the TUI paints nothing, and we implement getPeerUid/getPeerPid/setDumpable with FreeBSD system calls. It also needs a loader for the bundled .md/.txt assets, which stock Bun returns as module objects rather than text.
  • Where this issue comes in. Plain bun reads .env and bunfig.toml from its working directory, which your standalone executable turns off at compile time. We pass --no-env-file --config=/dev/null, and we use CLAUDE_CODE_PROCESS_WRAPPER to give Claude Code's own child processes the same options. That covers every self-spawn except the two above. Their children still load a project's .env (which can set ANTHROPIC_BASE_URL, for example) or run a bunfig.toml preload. We tell users about it at install time, but it would be much nicer if the wrapper simply covered them.

If a FreeBSD build, or some supported way of running the bundle on stock Bun, is ever on the table, we'd be happy to help test it. Thank you for Claude Code, and for considering this.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗