pytest ImportError: No module named 'src' in CI but works locally - sys.path issue
Fixes pytest ImportError: No module named 'src' that appears only in CI. Use when tests import from a src layout locally but CI cannot resolve it. Covers comparing sys.path in both environments, installing the package properly in CI, and declaring pythonpath in the pytest config. Not for missing third-party dependencies.
TL;DR
Pytest resolves imports from sys.path, and locally something puts src on the path: the shell's working directory, an editable install, or a .pth file. CI starts clean, so none of those exist and the import fails. The durable fix is to make the package properly installed in CI with pip install -e ., or to declare pythonpath in the pytest configuration, and then to remove the local crutch so both environments resolve imports the same way.
The query
pytest ImportError: No module named 'src' in CI but works locally - sys.path issueUse this when
- pytest fails in CI with ImportError: No module named 'src'
- tests import from a src/ layout
- the suite passes locally from the repo root but nowhere else
Not for
- a third-party package is missing in CI (that is a requirements problem, not sys.path)
- the module name is misspelled or the file was deleted
- imports fail in both environments identically
Steps
1. Print sys.path in both environments
Compare what Python sees locally versus in CI. The missing entry is the whole diagnosis.
python -c "import sys, pprint; pprint.pprint(sys.path)"- name: Debug sys.path
run: python -c "import sys, pprint; pprint.pprint(sys.path)"Expected output: the CI output is missing the entry (usually the repo root or src) that local runs have.
2. Check how the import is written and where src lives
Look at the failing import and the repo layout. A src layout without an install is the classic shape of this bug.
head -5 tests/test_api.py
ls src/Expected output: you confirm the tests do import src.something and that src/ is a package directory, not an installed distribution.
3. Install the package in CI, or declare pythonpath
Prefer a real install: it matches production. The pythonpath ini option is the fallback when an install is impractical.
pip install -e .# pytest.ini or setup.cfg / pyproject fallback
[pytest]
pythonpath = srcExpected output: python -c "import src" succeeds in the CI job and pytest collects the suite.
4. Remove the local-only crutch
If local runs depended on running pytest from a magic directory or on a stray .pth file, switch local development to the same install so the two environments cannot drift again.
pip install -e . # locally too
pytestExpected output: local and CI both pass with the same mechanism, and the bug cannot recur silently.
Tool compatibility
- pytest 7 - 8
- Python 3.9 - 3.13
- setuptools or hatchling editable installs
Variant phrasings
- ModuleNotFoundError: No module named 'src' in CI
- ImportError: No module named src (works locally)
- pytest E ModuleNotFoundError on src layout in GitHub Actions
Why it happens
Python builds sys.path from the script directory, PYTHONPATH, and installed distributions. Local workflows accidentally satisfy this by running from the repo root or by an old editable install; CI checkouts are clean, so the implicit path entries vanish. Declaring the mechanism explicitly, via an install or pythonpath, removes the dependence on ambient state.
Edge cases / pitfalls
- a conftest.py at the repo root can mask this locally because pytest inserts rootdir entries; CI may compute a different rootdir
- src layouts need an init.py or a PEP 420 namespace setup the build backend understands, or the editable install will not expose the package
- editable installs require a build backend that supports them; pin setuptools if the install step starts failing after an upgrade
- PYTHONPATH set globally in the CI image can shadow your src with a stale copy; prefer the explicit install
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_62z7L9h-ODC-bDSG3hgt4w