> ## Documentation Index
> Fetch the complete documentation index at: https://codspeed.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Profiling System Processes on macOS

> Why macOS System Integrity Protection prevents CodSpeed from profiling Apple system binaries, and how to work around it.

On macOS, CodSpeed profiles a benchmark by injecting a small profiling library
into each process of the benchmark's process tree, using the
`DYLD_INSERT_LIBRARIES` environment variable.

[System Integrity Protection (SIP)](https://support.apple.com/en-us/102149)
strips all `DYLD_*` environment variables whenever a protected Apple system
binary is executed, e.g., `/bin/sh`, `/usr/bin/env`, the system
`/usr/bin/python3`, or the built-in coreutils. Once such a binary is executed,
the profiling library is removed from that process and every process it spawns.
**The entire subtree becomes invisible to the profiler.**

## What this looks like

In the following execution tree, 🔍 marks a profiled process and 🔒 marks a
SIP-protected system binary. Processes without an icon are not profiled:

```text title="Execution tree" theme={null}
codspeed run
├─ 🔍 /opt/homebrew/bin/python3 bench.py    profiled
└─ 🔒 /bin/bash -c "python3 bench.py"       SIP strips DYLD_*
   └─ python3 bench.py                      not profiled
```

JavaScript tooling is particularly affected: package manager and
`node_modules/.bin` shims go through `/usr/bin/env` and `/bin/sh`, so the
profiling variable is stripped several times along the chain:

```text title="Execution tree (pnpm run bench)" theme={null}
codspeed run
└─ 🔒 /usr/bin/env -> node
   └─ 🔍 node (pnpm CLI)
      └─ 🔒 /bin/sh -> /bin/bash
         └─ 🔍 node (vitest runner)
            └─ 🔍 node × 5 workers
```

<Note>
  CodSpeed re-sets `DYLD_INSERT_LIBRARIES` when launching benchmark runtimes
  such as Node.js and Go, re-enabling profiling for the rest of the process
  tree. Only the SIP-protected system processes themselves are never profiled.
</Note>

## When to ignore the warning

Check which binaries the warning mentions:

* If they are launchers like `/bin/sh` or `/usr/bin/env` and your flamegraph
  contains your benchmark code, ignore the warning. The launcher only accounts
  for a negligible amount of time.
* If the work you want to profile happens inside a system binary itself, e.g.,
  you benchmark the system `/usr/bin/python3`, use one of the workarounds below.

## Workarounds

### Use a non-system toolchain

The recommended fix is to make sure no SIP-protected binary appears in the
benchmark's process tree. This requires no system changes and is the only
workaround that also applies in CI.

SIP only protects the binaries that ship with macOS, under `/usr/bin`, `/bin`,
`/usr/sbin`, and `/sbin`. Toolchains installed by a package manager, e.g.,
Homebrew, `uv`, `pyenv`, `nvm`, or `rustup`, live outside these paths and are
profiled normally:

1. Install the toolchain with a package manager instead of using the system one
   under `/usr/bin`.
2. Invoke it directly, so the kernel never executes a protected binary, e.g.,
   `uv run python3 bench.py` instead of `python3 bench.py`.

### Disable SIP

For local profiling on a machine you control, you can disable the part of SIP
that strips `DYLD_*` variables. With it disabled, system binaries are profiled
like any other process.

Follow
[Apple's instructions for disabling System Integrity Protection](https://developer.apple.com/documentation/security/disabling-and-enabling-system-integrity-protection):
boot into Recovery Mode, run one of the following commands in the Terminal, and
reboot:

```sh theme={null}
csrutil enable --without debug   # disable only the debugging restrictions
csrutil disable                  # disable SIP entirely
```

Prefer the first command: the debugging restrictions are the part of SIP that
strips `DYLD_*` variables, so disabling only them keeps the filesystem and
kernel extension protections in place.

To re-enable SIP when you are done, boot into Recovery Mode again and run:

```sh theme={null}
csrutil enable
```

Verify the current status at any time with: `csrutil status`.

## Profiling in CI

SIP cannot be disabled on CI-hosted macOS runners, e.g., GitHub-hosted macOS
runners. The only available workaround is to
[use a non-system toolchain](#use-a-non-system-toolchain): point the benchmark
command at the exact non-system toolchain binary and avoid `env` or shell shims
where possible.

System processes will not appear in CI profiles, and that is expected. It does
not affect the correctness of the benchmark measurements for your own code.

## Next Steps

<CardGroup cols={2}>
  <Card title="Walltime Instrument" icon="stopwatch" href="/instruments/walltime">
    Learn how the walltime instrument measures real-world execution time
  </Card>

  <Card title="Profiling" icon="bars-sort" href="/features/profiling">
    Learn how to read flamegraphs and use profiling data to optimize your code
  </Card>
</CardGroup>
