Fix: Hatch Not Working — Environment Errors, Build Backend, and pyproject.toml Issues
Quick Answer
How to fix Hatch errors — hatch env create fails, scripts not found, build backend hatchling missing, version not detected, plugin install errors, and publishing to PyPI.
The Error
You install Hatch and try to create an environment — nothing happens:
$ hatch env create
# Silent. No output. Did it work?Or you define scripts in pyproject.toml and they’re not found:
$ hatch run test
Command not found: testOr the build fails because Hatchling can’t determine the version:
ValueError: Unable to determine version. Check that 'src/mypkg/__init__.py' contains __version__Or you migrate from Poetry/setuptools and hatch build succeeds but the wheel is empty:
$ unzip -l dist/mypkg-1.0.0-py3-none-any.whl
# wheel only contains metadata — no Python filesOr hatch publish to PyPI fails with auth errors:
HTTPError: 403 ForbiddenHatch is the official PyPA project manager — it manages virtual environments, runs scripts, builds packages, and publishes to PyPI. Unlike Poetry (third-party) or uv (Rust-based newcomer), Hatch is maintained by the PyPA team that designs Python packaging standards. The two halves — hatch (CLI/env manager) and hatchling (build backend) — confuse newcomers because they’re usually used together but are separate tools. This guide covers each common failure.
Why This Happens
Hatch creates isolated virtual environments for each named env in pyproject.toml. The first hatch env create or hatch run lazily creates the env and installs dependencies — the silent output is normal completion. Scripts defined under [tool.hatch.envs.default.scripts] only work via hatch run <script>, not as raw shell commands.
Hatchling (the build backend) needs an explicit version source — it doesn’t auto-detect __version__ unless you configure [tool.hatch.version]. Empty wheels happen when the package discovery config points at the wrong directory.
Fix 1: Installing Hatch
# Standalone install (recommended — isolated, includes interpreter)
brew install hatch # macOS
pipx install hatch # Cross-platform
uv tool install hatch # Via uv
pip install --user hatch # Via pip (may conflict with project envs)Verify install:
hatch --version
hatch python list # Shows available Pythons Hatch can useHatch is BOTH a project manager AND a venv manager — it can install Python interpreters too:
hatch python install 3.12 # Download and install Python 3.12
hatch python install all # Install all supported versions
hatch python show # Show installed PythonsUseful when system Python isn’t recent enough. Hatch downloads from python.org / python-build-standalone.
Common Mistake: Installing Hatch via pip install hatch into the same venv as the project being managed. This creates a circular dependency — when Hatch tries to recreate the env, it’d uninstall itself. Use pipx, uv tool install, or brew to install Hatch globally and isolated.
Fix 2: Minimal pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "mypackage"
dynamic = ["version"]
description = "My awesome package"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [
{ name = "Your Name", email = "[email protected]" },
]
dependencies = [
"requests>=2.31",
"pydantic>=2.0",
]
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
docs = ["sphinx", "furo"]
[project.scripts]
mycli = "mypackage.cli:main" # Console script entry point
[tool.hatch.version]
path = "src/mypackage/__init__.py"
[tool.hatch.envs.default]
dependencies = ["pytest", "ruff", "mypy"]
[tool.hatch.envs.default.scripts]
test = "pytest {args:tests}"
lint = "ruff check ."
format = "ruff format ."
type-check = "mypy src/"
all = ["lint", "type-check", "test"]Project layout Hatchling expects by default:
my-project/
├── pyproject.toml
├── README.md
├── src/
│ └── mypackage/
│ ├── __init__.py # Contains __version__ = "1.0.0"
│ └── cli.py
└── tests/
└── test_basic.pyBuild and check:
hatch build # Creates dist/*.whl and dist/*.tar.gz
hatch build --clean # Clean dist/ first
unzip -l dist/*.whl # Verify wheel contents include your packageFix 3: Environments and Scripts
hatch env create # Create the default env
hatch env show # List all envs
hatch shell # Activate the default env in a subshell
hatch env remove default # Delete an envRun scripts defined in pyproject.toml:
hatch run test # Runs the "test" script in default env
hatch run lint # Runs lint
hatch run all # Runs lint, type-check, test in sequence
hatch run test -- -v # Pass extra args to pytest via --Multiple environments for testing matrix:
[tool.hatch.envs.default]
dependencies = ["pytest"]
[[tool.hatch.envs.test.matrix]]
python = ["3.10", "3.11", "3.12", "3.13"]
deps = ["pydantic-1", "pydantic-2"]
[tool.hatch.envs.test.overrides]
matrix.deps.dependencies = [
{ value = "pydantic<2", if = ["pydantic-1"] },
{ value = "pydantic>=2", if = ["pydantic-2"] },
]hatch env show # Lists test.py3.10-pydantic-1, test.py3.10-pydantic-2, ...
hatch run test:pytest # Runs pytest in EVERY matrix comboEquivalent to Tox/Nox matrix without the separate config file.
Common Mistake: Running pytest directly instead of hatch run test. Without hatch run, you’re using whatever pytest is on your system PATH — not necessarily the one in the project env. The test passes/fails locally but fails differently in CI because the dependency versions differ. Always use hatch run to ensure the right env.
Fix 4: Version Management
[tool.hatch.version]
path = "src/mypackage/__init__.py"# src/mypackage/__init__.py
__version__ = "1.2.3"Hatch reads __version__ and uses it for builds — no separate version file needed.
Bump versions:
hatch version # Show current version
hatch version patch # 1.2.3 → 1.2.4
hatch version minor # 1.2.3 → 1.3.0
hatch version major # 1.2.3 → 2.0.0
hatch version 2.0.0a1 # Set explicit versionPro Tip: Use hatch version to bump versions instead of editing __init__.py manually. It updates the file, normalizes the version string (PEP 440), and validates the format. Combined with conventional commits and CI tagging, this gives you a clean release workflow without separate version-management tools.
Version from git tags (alternative):
[tool.hatch.version]
source = "vcs" # Read from git tagpip install hatch-vcs # Required plugin for VCS-based versionsNow hatch build reads version from git describe — no __version__ needed in code.
Fix 5: Build Targets — wheel and sdist
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]
[tool.hatch.build.targets.sdist]
include = [
"src/",
"tests/",
"README.md",
"LICENSE",
]
exclude = [
"**/*.pyc",
"**/__pycache__",
]Common Mistake: Empty wheels happen when Hatchling can’t find your package. Default discovery looks for src/<package_name>/ matching the project name. If your layout differs, set packages = [...] explicitly:
# Flat layout (no src/)
[tool.hatch.build.targets.wheel]
packages = ["mypackage"]
# Different name than project
# Project name: "my-package"; actual import name: "myPackage"
[tool.hatch.build.targets.wheel]
packages = ["src/myPackage"]Verify the wheel includes your code:
hatch build
unzip -l dist/mypackage-*.whl
# Should show: mypackage/__init__.py, mypackage/cli.py, etc.If only *.dist-info/ files appear, your packages config is wrong.
Include non-Python files:
[tool.hatch.build.targets.wheel.force-include]
"src/mypackage/templates" = "mypackage/templates"
"src/mypackage/data/config.json" = "mypackage/data/config.json"Fix 6: Type Hints and Editable Installs
hatch env create # Creates env with package installed in editable mode by default
hatch shell # Activate; your local code changes are reflectedHatch installs your project in editable mode automatically — changes to source files take effect without reinstall.
For type checking with stubs, include a py.typed marker:
[tool.hatch.build.targets.wheel.shared-data]
"src/mypackage/py.typed" = "mypackage/py.typed"touch src/mypackage/py.typedThis tells mypy/pyright/Pylance that your package ships with type hints. Without it, type checkers treat your code as untyped.
Stub-only packages (when distributing type stubs separately):
[project]
name = "types-mypackage"
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage-stubs"]Fix 7: Publishing to PyPI
# Build
hatch build
# Publish to PyPI (requires API token)
hatch publish
# Publish to TestPyPI first (recommended for new packages)
hatch publish -r testConfigure credentials via env vars:
# PyPI
export HATCH_INDEX_AUTH=pypi-<your-api-token>
# Or per-repo
export HATCH_INDEX_REPO=https://upload.pypi.org/legacy/Or via Hatch config:
# pyproject.toml — public; don't put real tokens here
[tool.hatch.publish.indexes.main]
url = "https://upload.pypi.org/legacy/"# ~/.config/hatch/config.toml — keep tokens here
[publish.index.repos.main]
url = "https://upload.pypi.org/legacy/"
user = "__token__"
auth = "pypi-AgEIcHlwaS5vcmc..."Trusted publishers (GitHub Actions, no token needed):
# .github/workflows/publish.yml
name: Publish
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for trusted publishing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install hatch
- run: hatch build
- uses: pypa/gh-action-pypi-publish@release/v1Configure trusted publisher in PyPI project settings — no API token in GitHub Secrets needed.
Pro Tip: Always publish to TestPyPI first when a package is new or has structural changes. Install from TestPyPI, verify the install works, then publish to real PyPI. Mistakes on real PyPI (wrong file contents, broken metadata) can’t be fixed by re-uploading — versions are immutable once published.
Fix 8: Plugins and Custom Build Hooks
Hatch supports plugins via [tool.hatch.build.hooks.custom]:
[tool.hatch.build.hooks.custom]
path = "hatch_hooks.py"# hatch_hooks.py
from hatchling.plugin import hookimpl
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomBuildHook(BuildHookInterface):
def initialize(self, version, build_data):
# Runs before the build
# e.g., generate code, compile assets
print(f"Building {self.metadata.name} {version}")Pre-built plugins:
| Plugin | Purpose |
|---|---|
hatch-vcs | Version from git tags |
hatch-fancy-pypi-readme | Generate PyPI README from multiple sources |
hatch-requirements-txt | Read deps from requirements.txt |
hatchling-build-cuda | Build CUDA extensions |
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "vcs"Still Not Working?
Hatch vs Poetry vs uv
- Hatch — PyPA-official, supports matrix envs, decent default build backend. Best for libraries published to PyPI.
- Poetry — Mature, opinionated, single-file lock format. Best for application development with strict reproducibility. See Poetry dependency conflict.
- uv — Rust-based, extremely fast, growing rapidly. Best for performance-sensitive workflows. See uv not working.
For new library projects, Hatch is a safe default. For applications, uv has the strongest momentum. Poetry remains common in older codebases.
Migration from setup.py / setup.cfg
Hatch / Hatchling read entirely from pyproject.toml. Migrate by:
- Move
setup.pymetadata to[project]inpyproject.toml - Move
install_requirestodependencies - Move
extras_requireto[project.optional-dependencies] - Move
entry_pointsto[project.scripts]and[project.entry-points] - Delete
setup.pyandsetup.cfg
Most modern build backends (hatchling, setuptools, flit, pdm-backend) read identical [project] metadata — switching backends is a one-line change in [build-system].
CI Setup with Matrix Testing
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install hatch
- run: hatch run testFor tox-based testing workflows that Hatch can replace, see Tox not working. For pytest fixture patterns that pair with Hatch environments, see pytest fixture not found.
Environment Storage Location
Hatch stores envs in a shared location by default (not in the project directory). View where:
hatch env find # Path to current env
hatch env find default # Path to a specific named envDefault location:
- macOS/Linux:
~/Library/Application Support/hatch/env/virtual/... - Linux (XDG):
~/.local/share/hatch/env/virtual/... - Windows:
%APPDATA%/hatch/env/virtual/...
Use project-local envs by setting:
[tool.hatch.envs.default]
type = "virtual"
path = ".venv"Now Hatch creates .venv/ in the project root — friendly for VS Code’s auto-detection, easier to delete with rm -rf .venv.
Custom Build Backends
Hatchling is the default but Hatch can drive any PEP 517 backend:
[build-system]
requires = ["setuptools>=68", "setuptools-scm"]
build-backend = "setuptools.build_meta"Hatch still manages envs and scripts even with a non-Hatchling backend. Useful when migrating gradually from setuptools.
Lock Files
Hatch doesn’t have built-in lock file support (as of v1.13). For deterministic CI, either:
- Pin all dependencies in
pyproject.toml(requests==2.31.0, not>=2.31) - Use
pip-toolsto generate arequirements.lockfrompyproject.toml - Use
uv pip compile pyproject.toml -o requirements.lock
If lock files are essential, Poetry or uv provide first-class support.
Combining with Pre-commit
For pre-commit integration that runs Hatch scripts on commit, see pre-commit not working:
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: hatch-test
name: Run tests
entry: hatch run test
language: system
pass_filenames: false
stages: [pre-push]Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Pipenv Not Working — Lock File Generation, Shell Activation, and Dependency Resolution
How to fix Pipenv errors — pipenv lock takes forever, Pipfile.lock not generated, shell activation broken, no virtualenv created, dependency conflict, and migration to uv or Poetry.
Fix: PDM Not Working — Lock File Errors, PEP 582 Confusion, and Script Issues
How to fix PDM errors — pdm install fails resolving dependencies, lock file outdated warning, __pypackages__ vs venv confusion, pdm run script not found, build backend missing, and dependency groups setup.
Fix: uv Not Working — Command Not Found, Python Version Error, and Lock File Conflicts
How to fix uv errors — uv command not found after install, no Python interpreter found, uv run vs activate confusion, uv.lock merge conflicts, uv pip vs uv add, migrating from pip and Poetry, and workspace resolution failures.
Fix: joblib Not Working — Parallel Backends, Memory Cache, and Pickling Errors
How to fix joblib errors — Parallel n_jobs slower than expected, Memory cache miss, backend loky vs threading vs multiprocessing, pickling lambda not supported, dump load file size, and pytest interference.