# What is PEP 829?


[PEP 829: Package Startup Configuration Files](https://peps.python.org/pep-0829/) splits the two jobs that `.pth` files do today into two separate files. `.pth` keeps the `sys.path` extension job. A new `<name>.start` file takes over package initialization, replacing the `import` line that turns every `.pth` file into an `exec()` surface at interpreter startup. Authored by Barry Warsaw and accepted on 24 April 2026 for Python 3.15. Python 3.15 reached beta 1 on 7 May 2026, the first build to ship the `.start` mechanism for package authors to test against.

## Separate the two jobs `.pth` files do

A `.pth` file in `site-packages` does two unrelated things. Lines that name a directory get added to `sys.path`, which is the benign use that editable installs and namespace packages rely on. Lines that begin with the word `import` are passed to `exec()` at every interpreter startup. Both jobs travel in the same file, with no schema and no boundary between them.

The PEP is direct about why that matters: "import lines are executed using `exec()` during interpreter startup, which opens a broad attack surface."

The handbook already covers this surface in detail. [Why installing a Python package can run code](https://pydevtools.com/handbook/explanation/why-installing-a-python-package-can-run-code.md) walks through the four execution surfaces a package gets, and the [March 2026 litellm compromise](https://pydevtools.com/blog/litellm-supply-chain-attack-and-securing-python-dependencies.md) is the canonical example of the `.pth` `import`-line job being weaponized: a file called `litellm_init.pth` ran on every `python` command, including `python -c "print(1)"` in unrelated terminals, and exfiltrated credentials before the import returned.

PEP 829 does not remove the surface from existing Python releases, but it ends its future. Python 3.15 introduces the replacement, and three releases later the old mechanism stops running.

## Define the `.start` file format

A `<name>.start` file lives next to `.pth` files in `site-packages`. Each line is one entry point, written in the colon form that [`pkgutil.resolve_name()`](https://docs.python.org/3/library/pkgutil.html#pkgutil.resolve_name) accepts, for example `mypkg.startup:initialize`. The interpreter resolves each name and calls it. The two-file split is intentional: `.start` files cannot extend `sys.path`, and `.pth` files no longer need an `import` line for any new use case.

The `<name>` prefix is arbitrary and need not match the package, though the PEP recommends matching for clarity. Files are encoded as UTF-8 with an optional byte-order mark (the `utf-8-sig` codec). Lines beginning with `#` are comments. Within a `site-packages` directory the interpreter sorts `.start` files alphabetically by filename and runs every entry point in order; entries are not de-duplicated, so a callable listed twice runs twice. Errors during parsing are skipped silently and only surface when Python is invoked with `-v`. Errors raised by an entry point print a traceback to `sys.stderr` and processing continues.

The order of work at startup is: collect every `.pth` file, apply all `sys.path` extensions, then collect every `.start` file and run its entry points. All path changes are visible by the time any callable runs, so a `.start` entry point can import modules from a sibling package's `.pth`-extended path.

## Walk the deprecation timeline

PEP 829 retires the `.pth` `import` line in three phases.

- Python 3.15 through 3.17. Both file formats are supported. A `<name>.pth` file's `import` lines are still executed, *unless* a matching `<name>.start` file exists in the same directory. When the names match, the `.start` file shadows the `.pth` import lines and only the `.start` entry points run. The `.pth` file's directory lines still extend `sys.path` either way.
- Python 3.18 and 3.19. Import lines in `.pth` files are silently ignored. A package that still ships only a `.pth` file with `import` lines stops running its initialization code. No error, no warning unless `python -v` is set.
- Python 3.20 and later. Python emits a warning whenever it sees an `import` line in a `.pth` file. The warning runs in addition to the silent ignore.

A `.pth` file that contains only `sys.path` directory lines is unaffected throughout. The deprecation targets `import` lines specifically, which is the line type that gets passed to `exec()`.

## Migrate as a package author

Most packages do not ship `.pth` files at all. The two common cases that do are editable installs (covered by [PEP 660](https://pydevtools.com/handbook/explanation/what-is-pep-660.md)) and tools that need to run a bit of setup code at every interpreter startup. The first case is purely path extension, so it is unaffected. The second case is the one PEP 829 changes.

A package that needs initialization to run on every Python startup, and that needs to support both pre-3.15 and 3.15+ Pythons, ships both files:

- A `<name>.pth` whose `import` lines run on Pythons that predate PEP 829.
- A `<name>.start` whose entry points run on Python 3.15 and later.

On 3.15 through 3.17 the matching `<name>.start` shadows the `<name>.pth` import lines, so the callable runs once per startup, not twice. On 3.18 and later the `.pth` import lines are ignored anyway, so only the `.start` entry points run. A package that drops support for Pythons predating 3.15 can ship only the `.start` file.

The migration also forces a small refactor: anything the `.pth` `import` line used to do has to live behind a real callable somewhere in the package, which is the point. An entry point referenced by a callable name is auditable and shows up in static analysis; an `exec()` of an `import` statement does not.

## Defend before 3.18 lands

PEP 829 closes the `.pth` `import`-line surface from the runtime side, but the closure is gradual. The first release that silently ignores `import` lines is Python 3.18. Until 3.18 reaches the platforms a project actually runs on, a malicious release can still ship a `.pth` file that the interpreter dutifully executes, and the [defenses laid out in the security explainer](https://pydevtools.com/handbook/explanation/why-installing-a-python-package-can-run-code.md) still apply: [hash-pinned dependencies](https://pydevtools.com/handbook/how-to/how-to-pin-dependencies-with-hashes-in-uv.md), [a cooldown on recently-published versions](https://pydevtools.com/handbook/how-to/how-to-protect-against-python-supply-chain-attacks-with-uv.md), and a startup-file scanner like [`fetter`](https://github.com/fetter-io/fetter-py).

The other three execution surfaces ([install-time backends, import-time `__init__.py`, and `sitecustomize`/`usercustomize`](https://pydevtools.com/handbook/explanation/why-installing-a-python-package-can-run-code.md)) are not addressed by PEP 829 and remain available to a malicious release on every supported Python.

## Learn More

- [PEP 829 specification](https://peps.python.org/pep-0829/) is the authoritative document.
- [Python `site` module documentation](https://docs.python.org/3/library/site.html) describes `.pth`, `sitecustomize`, and `usercustomize` semantics that PEP 829 modifies.
- [`pkgutil.resolve_name()` documentation](https://docs.python.org/3/library/pkgutil.html#pkgutil.resolve_name) explains the colon-form names that `.start` files use.
- [Why installing a Python package can run code](https://pydevtools.com/handbook/explanation/why-installing-a-python-package-can-run-code.md) covers the four execution surfaces and why they matter.
- [LiteLLM Got Owned, and Your Dependencies Might Be Next](https://pydevtools.com/blog/litellm-supply-chain-attack-and-securing-python-dependencies.md) walks through the March 2026 `.pth`-`import` attack end to end.
- [Lightning Got Owned: When `import lightning` Steals Your Credentials](https://pydevtools.com/blog/lightning-pypi-compromise-import-time-supply-chain-attack.md) covers the import-time variant of the same playbook.
- [What is PEP 660?](https://pydevtools.com/handbook/explanation/what-is-pep-660.md) describes the modern editable-install standard, which uses `.pth` for `sys.path` extension only.
- [What is an editable install?](https://pydevtools.com/handbook/explanation/what-is-an-editable-install.md) gives the practical view of the most common legitimate `.pth` consumer.
- [Python Steering Council meeting summary, 2026-04-16](https://discuss.python.org/t/2026-04-16-python-steering-council-meeting-summary/107142) is the meeting where the PEP was queued for resolution.
