## 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
```text
pytest ImportError: No module named 'src' in CI but works locally  -  sys.path issue
```

## Use 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.
```bash
python -c "import sys, pprint; pprint.pprint(sys.path)"
```
```yaml
- 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.
```bash
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.
```bash
pip install -e .
```
```ini
# pytest.ini or setup.cfg / pyproject fallback
[pytest]
pythonpath = src
```
Expected 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.
```bash
pip install -e .   # locally too
pytest
```
Expected 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
