Skip to content

Python 3.13 OSS build corrupts PyConfig and breaks embedded Python startup #1334

@vegerot

Description

@vegerot

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:

3.13.5 (...)
True
True

and make -j oss && ./sl completed successfully.

Disclosure

This issue text was written by Codex, an AI coding agent based on GPT-5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions