# Wheel Target File Selection Errors in Hatchling

> When assisting users with wheel builds in Hatchling, reference this guide to help them understand and resolve file selection errors.

- Skill: `tools-only/wheel-target-file-selection-errors-in-hatchling` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/wheel-target-file-selection-errors-in-hatchling`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/wheel-target-file-selection-errors-in-hatchling/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-29
- Page: https://skillmd.com/skills/tools-only/wheel-target-file-selection-errors-in-hatchling

---


# Wheel Target File Selection Errors in Hatchling

## Overview

When assisting users with wheel builds in Hatchling, reference this guide to help them understand and resolve file selection errors. The wheel build target in Hatchling requires explicit or implicit file selection configuration. This document covers file selection errors, heuristics, and resolution strategies.

## Main Error: No File Selection Options

### Error Message (v1.19.0+)

```text
ValueError: At least one file selection option must be defined, see:
https://hatch.pypa.io/latest/config/build/
```

### Root Cause

The wheel builder cannot determine what files to include in the distribution when:

- No explicit file selection options are configured
- Default heuristics fail to identify package structure
- Project structure doesn't match common patterns

## File Selection Options

### 1. Packages Option (Recommended)

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]  # Explicit package directory
```

**Directory Structures:**

```text
# Standard layout
project/
├── pyproject.toml
├── src/
│   └── mypackage/
│       ├── __init__.py
│       └── module.py

# Flat layout
project/
├── pyproject.toml
├── mypackage/
│   ├── __init__.py
│   └── module.py
```

### 2. Include Option

```toml
[tool.hatch.build.targets.wheel]
include = [
    "mypackage",
    "data/*.json",
    "configs/*.yaml"
]
```

### 3. Only-Include Option (v1.4.0+)

```toml
[tool.hatch.build.targets.wheel]
only-include = ["src/mypackage"]
# More targeted than packages option
```

### 4. Force-Include Option

```toml
[tool.hatch.build.targets.wheel]
force-include = {
    "external/lib.so" = "mypackage/lib.so",
    "../shared/data.json" = "mypackage/data.json"
}
```

## Default Heuristics and When They Fail

### Working Default Heuristics

Hatchling attempts to auto-detect in this order:

1. **Single top-level package:**

   ```text
   project/
   ├── pyproject.toml
   ├── mypackage/
   │   └── __init__.py
   ```

2. **Src-layout:**

   ```text
   project/
   ├── pyproject.toml
   ├── src/
   │   └── mypackage/
   │       └── __init__.py
   ```

3. **Single module:**
   ```text
   project/
   ├── pyproject.toml
   ├── mymodule.py
   ```

### When Heuristics Fail

1. **Multiple packages without clear selection:**

   ```text
   project/
   ├── pyproject.toml
   ├── package1/
   │   └── __init__.py
   ├── package2/
   │   └── __init__.py
   ```

   **Solution:**

   ```toml
   [tool.hatch.build.targets.wheel]
   packages = ["package1", "package2"]
   ```

2. **Non-standard directory names:**

   ```text
   project/
   ├── pyproject.toml
   ├── core_lib/  # Not matching project name
   │   └── __init__.py
   ```

   **Solution:**

   ```toml
   [tool.hatch.build.targets.wheel]
   packages = ["core_lib"]
   ```

3. **Mixed content types:**

   ```text
   project/
   ├── pyproject.toml
   ├── lib.py
   ├── scripts/
   ├── data/
   ```

   **Solution:**

   ```toml
   [tool.hatch.build.targets.wheel]
   include = [
       "lib.py",
       "scripts/*.py",
       "data/*.json"
   ]
   ```

## Advanced Selection Scenarios

### Namespace Packages

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/company", "src/company/project"]
# Include all namespace levels
```

### Excluding Test Files

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/mypackage"]
exclude = [
    "**/tests/**",
    "**/*_test.py",
    "**/test_*.py"
]
```

### Including Non-Python Files

```toml
[tool.hatch.build.targets.wheel]
include = [
    "mypackage",
    "mypackage/**/*.json",
    "mypackage/**/*.yaml",
    "mypackage/**/*.txt"
]
```

## Build-Time File Generation

### Error: Files Generated by Build Hooks

**Problem:** Build hooks generate files after selection validation

**Solution 1: Use artifacts in build hook**

```python
# hatch_build.py
class CustomBuildHook(BuildHookInterface):
    def initialize(self, version, build_data):
        # Generate file
        generated_file = Path("src/mypackage/version.py")
        generated_file.write_text(f'__version__ = "{version}"')

        # Add as artifact
        build_data['artifacts'] = ['src/mypackage/version.py']
```

**Solution 2: Force-include with build hook**

```toml
[tool.hatch.build.targets.wheel]
force-include = {"generated/file.py" = "mypackage/file.py"}

[tool.hatch.build.hooks.custom]
# Hook generates the file before force-include runs
```

## Bypass Selection (Metadata-Only Wheels)

### Creating Empty Wheels (v1.20.0+)

```toml
[tool.hatch.build.targets.wheel]
bypass-selection = true
# Creates wheel with only metadata, no files
```

**Use Cases:**

- Metadata-only packages
- Virtual packages
- Namespace package roots

## Debugging File Selection

### 1. List Files That Will Be Included

```python
#!/usr/bin/env python3
"""Debug file selection for wheel building."""

from pathlib import Path
from hatchling.builders.wheel import WheelBuilder
from hatchling.metadata.core import ProjectMetadata

# Load metadata
metadata = ProjectMetadata(Path.cwd() / "pyproject.toml")

# Create wheel builder
builder = WheelBuilder(str(Path.cwd()), metadata=metadata)

# List files that will be included
for included_file in builder.recurse_included_files():
    print(f"Include: {included_file.path}")
```

### 2. Validate Selection Configuration

```python
#!/usr/bin/env python3
"""Validate wheel file selection configuration."""

import sys
import tomllib
from pathlib import Path

def validate_wheel_config():
    with open("pyproject.toml", "rb") as f:
        config = tomllib.load(f)

    wheel_config = (
        config.get("tool", {})
        .get("hatch", {})
        .get("build", {})
        .get("targets", {})
        .get("wheel", {})
    )

    # Check if any selection option is defined
    selection_options = [
        "packages",
        "include",
        "only-include",
        "force-include",
        "bypass-selection"
    ]

    has_selection = any(
        wheel_config.get(opt) for opt in selection_options
    )

    if not has_selection:
        print("ERROR: No file selection options defined!")
        print("Add one of:", ", ".join(selection_options))
        sys.exit(1)

    print("File selection configuration valid ✓")

if __name__ == "__main__":
    validate_wheel_config()
```

## Common Solutions by Project Type

### Library with Source Layout

```toml
[tool.hatch.build.targets.wheel]
packages = ["src/mylibrary"]
```

### Application with Multiple Packages

```toml
[tool.hatch.build.targets.wheel]
packages = [
    "app",
    "app_cli",
    "app_web"
]
```

### Data Science Project

```toml
[tool.hatch.build.targets.wheel]
include = [
    "notebooks/*.ipynb",
    "src/**/*.py",
    "data/*.csv",
    "models/*.pkl"
]
exclude = [
    "data/raw/**",
    "notebooks/scratch/**"
]
```

### Plugin System

```toml
[tool.hatch.build.targets.wheel]
only-include = ["plugins"]
force-include = {
    "plugin.yaml" = "plugins/plugin.yaml"
}
```

## Migration from v1.18 to v1.19+

### Old Behavior (v1.18)

- Silent fallback to empty wheel
- Implicit file inclusion
- No validation errors

### New Behavior (v1.19+)

- Explicit error when no files selected
- Force-include paths must exist
- Clear error messages with documentation links

### Migration Checklist

1. ✓ Add explicit `packages` configuration
2. ✓ Verify all force-included paths exist
3. ✓ Test build with `hatch build -t wheel`
4. ✓ Validate wheel contents: `unzip -l dist/*.whl`

## Error Resolution Flowchart

```text
Error: "At least one file selection option must be defined"
│
├─> Do you have a standard layout?
│   ├─> Yes: Add packages = ["src/packagename"]
│   └─> No: Continue
│
├─> Do you need specific files only?
│   ├─> Yes: Use only-include = [...]
│   └─> No: Continue
│
├─> Do you need pattern matching?
│   ├─> Yes: Use include = ["pattern/**/*.py"]
│   └─> No: Continue
│
├─> Do you need external files?
│   ├─> Yes: Use force-include = {...}
│   └─> No: Continue
│
└─> Metadata-only package?
    └─> Yes: Set bypass-selection = true
```

## Best Practices

1. **Always use explicit selection** for reproducible builds
2. **Prefer `packages`** for standard Python packages
3. **Use `only-include`** for precise control
4. **Validate configuration** before publishing
5. **Test wheel contents** after building

## Version History

- **v1.4.0**: Added `only-include` option
- **v1.19.0**: Added file selection validation error
- **v1.19.1**: Improved error messages
- **v1.20.0**: Added `bypass-selection` option

## Related Documentation

- [Path Validation](./path-validation.md)
- [Build Validation](./build-validation.md)
- [Heuristic Failures](./heuristic-failures.md)

