Summary
OSS builds of Sapling against Python 3.13 can produce an sl binary that fails during embedded Python startup.
The underlying issue appears to be an ABI/layout mismatch: python3-sys 0.7.2 defines PyConfig without fields added by CPython 3.13 (cpu_count and sys_path_0). When Sapling calls PyConfig_InitPythonConfig, CPython writes the full 3.13 C struct into the smaller Rust allocation. That can corrupt adjacent stack memory and/or make later direct field accesses use shifted offsets.
Environment
- OS: Linux
- Python:
/usr/bin/python3, observed with Python 3.13.5
- Build mode: OSS
- Build command:
make -j oss from eden/scm
Reproduction
From a fresh working copy on a system where /usr/bin/python3 is Python 3.13:
cd eden/scm
make -j oss
./sl
Python-backed commands can also reproduce the startup failure directly:
cd eden/scm
env CHGDISABLE=1 ./sl debugpython -- -c 'import sys; print(hasattr(sys, "path"))'
Observed Behavior
With a release build, the binary can panic after Python initialization because sys.path is missing:
thread 'main' panicked at lib/commands/cmdpy/src/hgpython.rs:143:69:
called `Result::unwrap()` on an `Err` value: PyErr { ptype: <class 'AttributeError'>, pvalue: Some(AttributeError("module 'sys' has no attribute 'path'")), ptraceback: None }
With a debug build, the failure may happen earlier inside Python initialization:
SystemError: null argument to internal routine
Fatal Python error: _PyImport_InitExternal: external importer setup failed
Python runtime state: core initialized
Current thread ... (most recent call first):
<no Python frame>
Expected Behavior
The OSS-built ./sl should initialize embedded Python normally on Python 3.13, including valid sys.path and sys.meta_path, and then run commands.
Suspected Cause
lib/commands/cmdpy/src/python.rs allocates ffi::PyConfig using the python3-sys 0.7.2 layout. That crate's final release predates CPython 3.13's added fields, so the Rust struct is smaller than CPython expects. The missing cpu_count field also shifts offsets for fields that come after it, such as executable, prefix, and module_search_paths.
Mitigation Tested Locally
A local workaround that fixed startup was to:
- allocate padded storage around
ffi::PyConfig before calling PyConfig_InitPythonConfig;
- avoid directly setting/accessing fields whose offsets are shifted on Python 3.13;
- let
PyConfig_Read compute executable automatically for the normal OSS dynamic-libpython path.
After that, this verification succeeded:
env CHGDISABLE=1 ./sl debugpython -- -c 'import sys; print(sys.version); print(hasattr(sys, "path")); print(hasattr(sys, "meta_path"))'
Output included:
and make -j oss && ./sl completed successfully.
Disclosure
This issue text was written by Codex, an AI coding agent based on GPT-5.
Summary
OSS builds of Sapling against Python 3.13 can produce an
slbinary that fails during embedded Python startup.The underlying issue appears to be an ABI/layout mismatch:
python3-sys 0.7.2definesPyConfigwithout fields added by CPython 3.13 (cpu_countandsys_path_0). When Sapling callsPyConfig_InitPythonConfig, CPython writes the full 3.13 C struct into the smaller Rust allocation. That can corrupt adjacent stack memory and/or make later direct field accesses use shifted offsets.Environment
/usr/bin/python3, observed with Python3.13.5make -j ossfromeden/scmReproduction
From a fresh working copy on a system where
/usr/bin/python3is Python 3.13:cd eden/scm make -j oss ./slPython-backed commands can also reproduce the startup failure directly:
Observed Behavior
With a release build, the binary can panic after Python initialization because
sys.pathis missing:With a debug build, the failure may happen earlier inside Python initialization:
Expected Behavior
The OSS-built
./slshould initialize embedded Python normally on Python 3.13, including validsys.pathandsys.meta_path, and then run commands.Suspected Cause
lib/commands/cmdpy/src/python.rsallocatesffi::PyConfigusing thepython3-sys0.7.2 layout. That crate's final release predates CPython 3.13's added fields, so the Rust struct is smaller than CPython expects. The missingcpu_countfield also shifts offsets for fields that come after it, such asexecutable,prefix, andmodule_search_paths.Mitigation Tested Locally
A local workaround that fixed startup was to:
ffi::PyConfigbefore callingPyConfig_InitPythonConfig;PyConfig_Readcomputeexecutableautomatically for the normal OSS dynamic-libpython path.After that, this verification succeeded:
env CHGDISABLE=1 ./sl debugpython -- -c 'import sys; print(sys.version); print(hasattr(sys, "path")); print(hasattr(sys, "meta_path"))'Output included:
and
make -j oss && ./slcompleted successfully.Disclosure
This issue text was written by Codex, an AI coding agent based on GPT-5.