Corporate launcher
Force every process Claude Code spawns from its own binary, including background agents, through a mandatory company launcher with processWrapper.
Some security teams insist that nothing runs on a workstation unless it starts through a company launcher: a script that enters a sandbox, applies network policy or injects credentials. A wrapper around claude on your PATH covers the sessions people start by hand, but it misses a lot. The background service behind agent view, every session it hosts, and Claude Code's own relaunches after an update all start from the binary's direct path without consulting PATH.
CLAUDE_CODE_PROCESS_WRAPPER (and the equivalent processWrapper setting) closes that gap. Set it to your launcher's absolute path and Claude Code runs the launcher, passing the real Claude Code command as arguments, for every process it starts from its own binary.
Note: The environment variable needs v2.1.208 or later; the
processWrappersetting needs v2.1.210 or later. Older versions silently ignore them and start everything unwrapped, so always run the verification step.
What is covered
With the launcher configured, these go through it:
- The background service that
claude agentsand background sessions start on demand. - The terminal host and the session inside every agent view row, including the warm standby sessions the service keeps ready.
- Sessions the service respawns after an update or crash.
- Claude Code's self-relaunch to finish an update, including agent view's restart-for-update.
- Session processes started by Remote Control (v2.1.210+).
- Split-pane teammates that agent teams open in tmux or iTerm2 (v2.1.210+).
Windows is not covered. The launcher contract relies on exec, which Windows lacks, so the variable is ignored there. Processes run unwrapped, and the only clue is a warning in the debug log. If your policy covers Windows, count those machines as unwrapped.
What still starts outside it
- An installed service written before the launcher existed.
launchdorsystemdstarts it from its unit file./statusandclaude daemon statuswarn about the mismatch; once the service restarts with the setting, its sessions are wrapped. - Sessions you start in a terminal. These run however you invoked them. Cover them with a script called
claudeearlier on yourPATHthat calls your launcher with the real binary. Do not replace the managed symlink. Because the service does not look upPATH, the two launchers do not stack. - The first process of a
claude-cli://deep link, which the OS protocol handler starts. Everything it spawns afterwards is wrapped. To close this path, setdisableDeepLinkRegistration(see deep links). --worktreewith--tmux, whose pane the multiplexer starts.- The Claude in Chrome native-messaging host, which the browser starts.
Side effect worth knowing: with a launcher in place, ps and Activity Monitor stop showing the claude bg-pty-host and claude bg-spare labels, because exec rebuilds the argument list. Nothing is hidden; Claude Code tracks its processes by binary path.
Setting it up
1. Write the launcher
It must be executable, live at an absolute path, and finish by replacing itself with Claude Code. A realistic example that loads short-lived proxy credentials from a cache and enforces an egress policy:
#!/bin/sh
# /opt/acme/bin/claude-launch
# Pull a cached proxy token (refreshed elsewhere, so this stays fast)
if [ -r "$HOME/.cache/acme/proxy-token" ]; then
ACME_PROXY_TOKEN=$(cat "$HOME/.cache/acme/proxy-token")
export ACME_PROXY_TOKEN
fi
# Apply the egress profile, then become Claude Code
exec /opt/acme/bin/egress-guard --profile developer -- "$@"
If egress-guard itself execs its trailing arguments, the chain still ends with Claude Code replacing the launcher. Then chmod +x /opt/acme/bin/claude-launch.
Warning: If you previously replaced the
~/.local/bin/claudesymlink with a launcher, restore the original symlink in the same change. Otherwise the first wrapped session starts the service through both launchers, and the install becomes "externally managed":/doctorcomplains, auto-update leaves the file alone and old-version cleanup stops.
2. Configure it in settings, not the shell
The background service starts on demand, outlives your shell and never reads shell profiles, so an export is not enough. Use the env block of ~/.claude/settings.json for one machine, or managed settings for the fleet:
{
"env": {
"CLAUDE_CODE_PROCESS_WRAPPER": "/opt/acme/bin/claude-launch"
}
}
Or, if you push settings as individual keys:
{
"processWrapper": "/opt/acme/bin/claude-launch"
}
Rules of precedence:
- The environment variable beats
processWrapperif both are set. - A managed value overrides
~/.claude/settings.jsonand shell exports, so users cannot swap in a different launcher. - Project and local settings can never configure it. A repository must not be able to put a binary in front of every Claude Code process, so the variable is ignored there (with a debug-log warning) and the key is never read from those files.
- Delivered through server-managed settings,
processWrapperappears in the security approval dialog alongside other settings that run admin-supplied executables.
3. Restart the service and sessions
A running service and open sessions read the value once at startup. Stop the on-demand service with claude daemon stop --any; the next claude agents or --bg starts a wrapped one. An installed service takes claude daemon stop without --any. Then restart open sessions.
On machines you cannot touch, the first new session after the settings push retires a leftover unwrapped on-demand service automatically. A machine where nobody starts a session keeps the old service until someone does, and installed services always need a manual restart.
4. Verify
In a session, /status has a Self-exec entry showing the resolved launch command, with a warning if the running service does not match. claude daemon status prints the same from the shell, and keeps doing so after you unset the variable.
The launcher contract
If the launcher cannot run, Claude Code refuses to start the process rather than run it unwrapped (except on Windows, as above). Your script must:
| Rule | Why |
|---|---|
End with exec "$@" (or exec something that execs it) | A launcher that forks and exits orphans Claude Code. Agent view marks that session failed, names the launcher and cleans up. |
| Leave arguments alone | The first argument is the binary and the rest is its argv. Do not reorder, drop or prepend. |
| Pass the whole inherited environment through | Session auth tokens, provider and model selection and the wrapper variable itself travel in the environment. Rebuilding it from an allowlist breaks sessions and shows as a launcher mismatch in /status. Adding variables is fine. If you enter a namespace that resets the environment, re-export everything inside it. |
Reach exec within about three seconds | A cold background dispatch runs the launcher twice in a row before any output. Do slow work like SSO exchanges lazily or from a cache. |
| Cope with running inside itself | Every nested self-spawn is wrapped too, so if the launcher takes an exclusive lock it must notice it already holds it. |
Print nothing before exec | Any output is reported as the crash cause if the session dies early. |
| Not depend on argument spelling | A flag value might arrive as --flag value or --flag=value, and that can change between versions. |
Value format
For most launchers the value is just a path. To give the launcher its own arguments, add them after the path. The value is parsed as an argument list, not a shell command:
- Whitespace separates tokens; double quotes group a token containing spaces.
- A value starting with
[is read as a JSON string array, for example["/opt/acme/bin/claude-launch", "--tier", "contractor"]. - No shell features: no variable expansion or globbing, and an unquoted
;,|,&or$(is rejected as a configuration error.
When the value cannot be used, Claude Code refuses to start the affected process and reports why; the errors page lists the launcher messages.
Not the same as CLAUDE_CODE_SHELL_PREFIX
These two are easy to confuse:
CLAUDE_CODE_PROCESS_WRAPPER | CLAUDE_CODE_SHELL_PREFIX | |
|---|---|---|
| Wraps | Claude Code's own processes | Shell commands Claude runs for you: Bash tool calls, hooks, stdio MCP server start commands |
| Receives the command as | Separate argv tokens to exec | One shell-quoted string in $1 to re-evaluate |
A script written for one will not work as the other. See environment variables.