Summary
The first API request a process makes triggers a fork()+exec() of uname -p,
via the lru-cached platform-header computation. In long-running macOS processes
that have initialized Apple networking state (any process doing HTTPS), the forked
child can segfault in Network.framework's atfork handler, producing a macOS
crash report (Python-*.ips) on every process lifetime's first request. The SDK
call itself succeeds — the failure is silent apart from the crash report — but
the side effect (an unexpected subprocess from a serving process) is surprising
and, in fork-hostile environments, harmful.
Chain
platform_headers() is @lru_cache(maxsize=None) (anthropic/_base_client.py)
— computed once per process, on the first request.
get_platform() calls platform.platform() (_base_client.py, the
X-Stainless-OS detection).
- CPython's
platform.platform() unpacks uname() including the lazy
processor field (platform.py:1212 in 3.11).
uname_result.processor resolves via _Processor.get(); there is no
get_darwin, so macOS falls through to _Processor.from_subprocess() —
subprocess.check_output(['uname', '-p']) (platform.py:753).
subprocess uses fork()+exec() here. If the parent has initialized
Network.framework (normal after any HTTPS traffic), the forked child can die
in _pthread_atfork_child_handlers → nw_settings_child_has_forked() →
SIGSEGV, before exec runs.
- The parent sees a
CalledProcessError, which the stdlib swallows
(platform.py:768), so the request proceeds with processor='' — nothing
in the SDK observes the failure.
Crash stack (child process, from macOS crash report)
0 libsystem_trace.dylib _os_log_preferences_refresh + 56
1 libsystem_trace.dylib os_log_type_enabled + 772
2 libnetworkextension.dylib NEFlowDirectorDestroy + 64
3 Network nw_path_release_globals + 164
4 Network nw_settings_child_has_forked() + 296
5 libsystem_pthread.dylib _pthread_atfork_child_handlers + 76
6 libsystem_c.dylib fork + 112
7 _posixsubprocess.cpython-311-darwin.so do_fork_exec + 68
8 _posixsubprocess.cpython-311-darwin.so subprocess_fork_exec + 928
... (Python eval frames: subprocess.check_output ← _Processor.from_subprocess
← platform.uname ← platform.platform ← get_platform ← platform_headers
← first client request)
Reproduction conditions
- macOS (observed on macOS 26.5 arm64), CPython 3.11.15 (Homebrew), anthropic 0.116.0.
- A long-running process (observed: a Streamlit dashboard alive ~9.5 h) that has
performed unrelated HTTPS traffic, then makes its first anthropic API call.
- The crash is state-dependent: freshly started processes fork cleanly; aged ones
produce the child segfault. We captured identical crash reports on two
consecutive days, each at the process's first API call. Subsequent calls never
fork (lru_cache), so it is once per process lifetime.
Why the child segfaults is Apple's bug — the ask here is narrower
Fork-without-exec in a Network.framework process is Apple's fragility, and the
uname -p subprocess is CPython's (platform.py has no get_darwin). But the
SDK is the only reason a typical serving process forks at all, and it does so
implicitly, at an unpredictable time (first request), from whatever thread makes
that request.
Suggested fixes
- Compute the OS string without triggering
uname().processor: build from
platform.system() / platform.release() / platform.machine() +
platform.mac_ver(), all of which are fork-free. X-Stainless-Arch already
uses platform.machine().
- Alternatively, resolve platform headers eagerly at import/client-construction
time rather than first-request time, so any fork happens while the process is
young and fork-safe.
Workaround for affected users
Call platform.processor() once at process startup (before significant network
activity). The stdlib caches uname_result, so the SDK's later call never forks.
Summary
The first API request a process makes triggers a
fork()+exec()ofuname -p,via the lru-cached platform-header computation. In long-running macOS processes
that have initialized Apple networking state (any process doing HTTPS), the forked
child can segfault in
Network.framework's atfork handler, producing a macOScrash report (
Python-*.ips) on every process lifetime's first request. The SDKcall itself succeeds — the failure is silent apart from the crash report — but
the side effect (an unexpected subprocess from a serving process) is surprising
and, in fork-hostile environments, harmful.
Chain
platform_headers()is@lru_cache(maxsize=None)(anthropic/_base_client.py)— computed once per process, on the first request.
get_platform()callsplatform.platform()(_base_client.py, theX-Stainless-OSdetection).platform.platform()unpacksuname()including the lazyprocessorfield (platform.py:1212in 3.11).uname_result.processorresolves via_Processor.get(); there is noget_darwin, so macOS falls through to_Processor.from_subprocess()—subprocess.check_output(['uname', '-p'])(platform.py:753).subprocessusesfork()+exec()here. If the parent has initializedNetwork.framework (normal after any HTTPS traffic), the forked child can die
in
_pthread_atfork_child_handlers→nw_settings_child_has_forked()→SIGSEGV, before
execruns.CalledProcessError, which the stdlib swallows(
platform.py:768), so the request proceeds withprocessor=''— nothingin the SDK observes the failure.
Crash stack (child process, from macOS crash report)
Reproduction conditions
performed unrelated HTTPS traffic, then makes its first
anthropicAPI call.produce the child segfault. We captured identical crash reports on two
consecutive days, each at the process's first API call. Subsequent calls never
fork (lru_cache), so it is once per process lifetime.
Why the child segfaults is Apple's bug — the ask here is narrower
Fork-without-exec in a Network.framework process is Apple's fragility, and the
uname -psubprocess is CPython's (platform.pyhas noget_darwin). But theSDK is the only reason a typical serving process forks at all, and it does so
implicitly, at an unpredictable time (first request), from whatever thread makes
that request.
Suggested fixes
uname().processor: build fromplatform.system()/platform.release()/platform.machine()+platform.mac_ver(), all of which are fork-free.X-Stainless-Archalreadyuses
platform.machine().time rather than first-request time, so any fork happens while the process is
young and fork-safe.
Workaround for affected users
Call
platform.processor()once at process startup (before significant networkactivity). The stdlib caches
uname_result, so the SDK's later call never forks.