pixi
te a# Python Setup with pixi - Complete Guide
Composite GitHub Action for setting up Python environments with the pixi package manager, designed for projects using conda-forge packages and cross-platform development with pyproject.toml.
Table of Contents¶
- Overview
- Quick Start
- Inputs Reference
- Features
- Usage Scenarios
- Basic Setup
- Multiple Environments
- Environment Activation
- Matrix Testing
- Complex Projects with Features
- Caching
- Lock File Management
- Lock File Verification
- Frozen vs Locked Modes
- Environment Architecture
- Caching Strategy
- Testing Guide
- Troubleshooting
- Best Practices
Overview¶
This action provides a complete Python environment setup using pixi, a fast package manager built on top of conda. It automatically:
- Installs pixi package manager
- Validates lock file existence
- Sets up one or more pixi environments
- Activates a specified environment
- Optionally caches environments for faster CI runs
- Verifies lock file integrity (configurable)
Key Features: - ✅ Support for conda-forge and other conda channels - ✅ Cross-platform compatibility (Linux, macOS, Windows) - ✅ Multiple environment support - ✅ Feature-based dependency management - ✅ Intelligent caching with lock file validation - ✅ Lock file verification modes (locked/frozen)
Location: serapeum-org/github-actions/actions/python-setup/pixi@v1
Quick Start¶
Basic Usage (Default Environment)¶
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
# The default environment is automatically activated!
- run: python --version
- run: pytest
With Multiple Environments¶
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'dev test prod'
activate-environment: 'dev'
- run: pytest # Runs in dev environment
With Caching Enabled¶
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true'
- run: python --version # Much faster on subsequent runs!
Inputs Reference¶
| Input | Description | Required | Default | Valid Values |
|---|---|---|---|---|
environments |
Pixi environments to install | No | 'default' |
Space-separated list (e.g., 'py311', 'dev test prod') |
activate-environment |
Environment to activate after installation | No | 'default' |
Any environment name from your pyproject.toml |
cache |
Enable caching of pixi environments | No | 'false' |
'true', 'false' |
verify-lock |
Verify lock file is up to date | No | 'true' |
'true', 'false' |
Input Details¶
environments¶
Specifies which pixi environments from [tool.pixi.environments] in pyproject.toml to install.
Default behavior ('default'): Installs only the default environment.
When specified: Installs all specified environments. This is useful when you need to prepare multiple environments or when running matrix tests.
Formats supported:
- Single environment: 'dev'
- Space-separated: 'dev test prod'
- Multiple runs: You can call the action multiple times with different environments (though not recommended - see best practices)
Example pyproject.toml:
[tool.pixi.environments]
default = ["core"]
dev = ["core", "dev-tools"]
test = ["core", "test-tools"]
prod = ["core"]
Usage:
# Install only dev environment
environments: 'dev'
# Install dev and test environments
environments: 'dev test'
# Install all environments
environments: 'default dev test prod'
activate-environment¶
Specifies which environment to activate in the GitHub Actions PATH after installation.
Default behavior ('default'): Activates the default environment.
Important: This should be ONE of the environments listed in the environments input. If you specify an environment that wasn't installed, the action will fail.
Example:
# Install multiple environments but activate only 'dev'
with:
environments: 'dev test prod'
activate-environment: 'dev'
After activation:
- Python and packages from the activated environment are available in PATH
- All subsequent steps use this environment
- You can run pixi run -e <env> to run commands in other environments
cache¶
Controls whether to cache the pixi environments directory (.pixi) for faster CI runs.
Default: 'false' - No caching by default to ensure clean environments unless explicitly requested.
When 'true':
- Caches the .pixi directory based on pixi.lock hash
- Cache is only written on push events to the main branch
- Significantly speeds up subsequent CI runs (from minutes to seconds)
- Requires pixi.lock file - will fail with a clear error if missing
Cache key structure: pixi-<hash-of-pixi.lock>
Cache write behavior: Only writes cache when:
- cache: 'true' is set
- Event is push (not PR)
- Branch is main
Example:
# Enable caching (recommended for production)
cache: 'true'
# Disable caching (useful for debugging)
cache: 'false'
verify-lock¶
Controls lock file verification behavior and pixi installation mode.
Default: 'true' - Always verify lock file is up to date.
When 'true' (locked mode):
- Uses pixi install --locked
- Verifies that pixi.lock matches pyproject.toml
- Fails if lock file is outdated - prevents installing mismatched dependencies
- Recommended for CI/CD to catch lock file drift
When 'false' (frozen mode):
- Uses pixi install --frozen
- Uses lock file as-is without verification
- Faster but won't catch outdated lock files
- Useful when you're certain the lock file is correct
Example:
# Verify lock file (default, recommended for CI)
verify-lock: 'true'
# Skip verification (faster, use cautiously)
verify-lock: 'false'
Features¶
1. Automatic Lock File Validation¶
The action always requires a pixi.lock file to be present in your repository. This ensures reproducible builds across all environments.
What happens:
1. ✅ Checks if pixi.lock exists
2. ❌ Fails with a clear error message if missing
3. 📝 Provides instructions on how to generate the lock file locally
Error message when lock file is missing:
❌ pixi.lock file does not exist!
To fix this issue:
Run 'pixi install' locally to generate pixi.lock and commit it to your repository
This action requires a committed lock file and will never generate it in CI.
Why this design? - Lock files should be generated on the developer's machine, not in CI - Ensures all developers and CI use the same dependency versions - Prevents CI from making commits or masking dependency conflicts - Makes dependency updates explicit and reviewable
2. Dual Mode Installation: Locked vs Frozen¶
The action supports two installation modes controlled by the verify-lock input:
Locked Mode (verify-lock: 'true' - default):
pixi.lock matches pyproject.toml
- Fails if dependencies were added/removed without updating lock file
- Best for: CI/CD pipelines, ensuring strict reproducibility
Frozen Mode (verify-lock: 'false'):
pixi.lock without verification
- Faster but won't detect lock file drift
- Best for: Local development, trusted environments
Comparison:
| Mode | Flag | Verifies Lock | Speed | Use Case |
|---|---|---|---|---|
| Locked | --locked |
✅ Yes | Slower | CI/CD, production |
| Frozen | --frozen |
❌ No | Faster | Development, trusted deploys |
3. Environment Information Logging¶
After installation, the action automatically logs environment information:
Pixi Information:
Installed Packages:
This appears in collapsible sections in GitHub Actions logs for easy debugging.
4. Intelligent Caching¶
When cache: 'true' is enabled:
Cache Behavior:
- Key: pixi-<hash(pixi.lock)>
- Cached: .pixi/ directory (all environments)
- Write: Only on push to main branch
- Read: All runs (including PRs)
Performance Impact: - 🐌 First run: ~5-10 minutes (install all packages) - 🚀 Cached run: ~30 seconds (restore cache + verification)
Cache Invalidation:
- Automatic when pixi.lock changes
- Manual: bump version in pyproject.toml or clear GitHub cache
5. Cross-Platform Support¶
The action works seamlessly across: - 🐧 Linux (ubuntu-latest) - 🍎 macOS (macos-latest, macos-13, macos-14) - 🪟 Windows (windows-latest)
Shell commands automatically adapt to the platform (bash on Linux/macOS, PowerShell on Windows).
6. Multiple Environment Management¶
Install and manage multiple environments in a single workflow:
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'py311 py312 py313'
activate-environment: 'py311'
# py311 is active by default
- run: python --version # Python 3.11
# Switch to another environment for specific commands
- run: pixi run -e py312 python --version # Python 3.12
- run: pixi run -e py313 pytest # Run tests in Python 3.13
Usage Scenarios¶
Basic Setup¶
Scenario: Simple project with a single environment.
pyproject.toml:
[project]
name = "my-project"
version = "0.1.0"
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "osx-64", "win-64"]
[tool.pixi.dependencies]
python = ">=3.11"
numpy = ">=1.26"
pytest = ">=7.0"
Workflow:
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup Python with pixi
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'false'
- name: Run tests
run: pytest
- name: Verify installation
run: |
python --version
python -c "import numpy; print(f'NumPy {numpy.__version__}')"
Result: Default environment is installed and activated, all packages available.
Multiple Environments¶
Scenario: Project with separate development and production environments.
pyproject.toml:
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "osx-64", "win-64"]
[tool.pixi.dependencies]
python = ">=3.11"
[tool.pixi.feature.core.dependencies]
numpy = ">=1.26"
pandas = ">=2.0"
[tool.pixi.feature.dev.dependencies]
pytest = ">=7.0"
black = ">=23.0"
ruff = ">=0.1"
[tool.pixi.feature.prod.dependencies]
gunicorn = ">=21.0"
[tool.pixi.environments]
default = ["core"]
dev = ["core", "dev"]
prod = ["core", "prod"]
Workflow:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup dev environment
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'dev'
activate-environment: 'dev'
cache: 'false'
- name: Run linting
run: |
black --check .
ruff check .
- name: Run tests
run: pytest
deploy-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup prod environment
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'prod'
activate-environment: 'prod'
cache: 'false'
- name: Verify prod dependencies
run: |
python -c "import gunicorn"
python -c "import numpy, pandas"
Result: Different jobs use different environments with appropriate dependencies.
Environment Activation¶
Scenario: Install multiple environments but work primarily in one.
pyproject.toml:
[tool.pixi.feature.core.dependencies]
python = ">=3.11"
requests = ">=2.31"
[tool.pixi.feature.dev.dependencies]
pytest = ">=7.0"
black = ">=23.0"
[tool.pixi.feature.test.dependencies]
pytest = ">=7.0"
pytest-cov = ">=4.0"
hypothesis = ">=6.0"
[tool.pixi.environments]
default = ["core"]
dev = ["core", "dev"]
test = ["core", "test"]
Workflow:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup all environments
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'dev test'
activate-environment: 'dev' # Dev is active by default
cache: 'false'
- name: Run linting (uses active dev environment)
run: black --check .
- name: Run tests (switch to test environment)
run: pixi run -e test pytest --cov
- name: Check coverage (in test environment)
run: pixi run -e test coverage report
Result: Multiple environments available, explicit control over which is active.
Matrix Testing¶
Scenario: Test across multiple Python versions or OS combinations.
pyproject.toml:
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"]
[tool.pixi.dependencies]
python = ">=3.11"
[tool.pixi.feature.py311.dependencies]
python = "3.11.*"
pandas = ">=2.0"
[tool.pixi.feature.py312.dependencies]
python = "3.12.*"
scikit-learn = ">=1.3"
[tool.pixi.environments]
default = []
py311 = ["py311"]
py312 = ["py312"]
Workflow:
jobs:
test-matrix:
name: Test Python ${{ matrix.python-env }} on ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-env: ['py311', 'py312']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Setup Python environment
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: ${{ matrix.python-env }}
activate-environment: ${{ matrix.python-env }}
cache: 'false'
- name: Run tests
run: |
python --version
pytest
Result: Tests run on 6 combinations (3 OS × 2 Python versions), each with correct environment.
Complex Projects with Features¶
Scenario: Large project with multiple feature sets (ML, visualization, development tools).
pyproject.toml:
[project]
name = "complex-ml-project"
version = "0.1.0"
[tool.pixi.workspace]
channels = ["conda-forge", "pytorch"]
platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"]
[tool.pixi.tasks]
test = "pytest tests/"
lint = "ruff check ."
train = "python scripts/train.py"
[tool.pixi.dependencies]
python = ">=3.11,<3.13"
[tool.pixi.feature.core.dependencies]
numpy = ">=1.26"
pandas = ">=2.0"
scipy = ">=1.11"
[tool.pixi.feature.ml.dependencies]
scikit-learn = ">=1.3"
xgboost = ">=2.0"
pytorch = ">=2.0"
[tool.pixi.feature.viz.dependencies]
matplotlib = ">=3.8"
seaborn = ">=0.13"
plotly = ">=5.18"
[tool.pixi.feature.dev.dependencies]
pytest = ">=7.0"
pytest-cov = ">=4.0"
black = ">=23.0"
ruff = ">=0.1"
mypy = ">=1.0"
[tool.pixi.environments]
default = ["core", "dev"]
ml = ["core", "ml", "viz", "dev"]
prod = ["core", "ml"]
ci = ["core", "ml", "dev"]
Workflow:
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup CI environment
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'ci'
activate-environment: 'ci'
cache: 'true' # Enable caching for complex environments
- name: Lint code
run: |
black --check .
ruff check .
mypy src/
- name: Run tests
run: pytest --cov=src tests/
- name: Test ML models
run: |
python -c "import sklearn, xgboost, torch"
python scripts/test_model.py
train-model:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup ML environment
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'ml'
activate-environment: 'ml'
cache: 'true'
- name: Train model
run: python scripts/train.py
- name: Generate visualizations
run: python scripts/visualize.py
Result: Different jobs use optimized environments with only necessary dependencies.
Caching¶
Scenario: Speed up CI by caching the pixi environment.
Without Caching¶
Workflow:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup Python
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'false' # No caching
- name: Run tests
run: pytest
Time: ~5-10 minutes per run (full package installation each time)
With Caching¶
Workflow:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Setup Python with caching
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true' # Enable caching
- name: Run tests
run: pytest
Time: - First run: ~5-10 minutes (installs packages + saves cache) - Subsequent runs: ~30 seconds (restores cache) - After lock file change: ~5-10 minutes (cache miss, rebuilds)
Cache behavior:
- ✅ Reads cache: All runs (PRs, pushes, manual)
- ✏️ Writes cache: Only on push to main branch
- 🔄 Updates cache: When pixi.lock changes
Validation Without Lock File¶
The action will fail if you enable caching without a lock file:
Workflow (will fail):
- name: Setup Python with caching
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true' # ❌ Will fail - no pixi.lock!
Error:
❌ Cache is enabled but pixi.lock file does not exist!
To fix this issue:
First run 'pixi install' locally to generate pixi.lock and then commit it to your repository
How to fix:
# On your local machine
pixi install # Generates pixi.lock
git add pixi.lock
git commit -m "Add pixi lock file"
git push
Lock File Management¶
Lock File Verification¶
The action implements a lock-file-first approach:
- Lock file is mandatory: Action fails immediately if
pixi.lockdoesn't exist - Generated locally: Developers run
pixi installon their machines - Committed to repository: Lock file is version-controlled
- Verified in CI: Action validates (optionally) that lock matches dependencies
Benefits: - 🔒 Reproducibility: Everyone uses exact same versions - 👀 Visibility: Lock file changes are reviewable in PRs - 🚫 No surprises: CI doesn't install different versions than local - ⚡ Speed: No lock resolution in CI
Frozen vs Locked Modes¶
The verify-lock input controls how pixi uses the lock file:
Locked Mode (verify-lock: 'true' - Default)¶
Command used: pixi install --locked
Behavior:
1. Reads pixi.lock
2. Compares with pyproject.toml
3. ❌ Fails if mismatch (dependencies added/changed without updating lock)
4. ✅ Installs exact versions from lock file
Use when: - Running CI/CD pipelines - You want strict reproducibility - You want to catch lock file drift
Example:
- name: Setup with verification
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
verify-lock: 'true' # Default
What it catches:
# pyproject.toml - added pandas
[tool.pixi.dependencies]
python = ">=3.11"
numpy = ">=1.26"
pandas = ">=2.0" # ⚠️ NEW!
# pixi.lock - doesn't have pandas
# ❌ Action fails: "Lock file is outdated!"
Frozen Mode (verify-lock: 'false')¶
Command used: pixi install --frozen
Behavior:
1. Reads pixi.lock
2. Installs exact versions from lock file
3. ✅ No verification - trusts lock file is correct
4. Faster (skips verification step)
Use when: - You're certain the lock file is up to date - You want faster CI runs - Lock file is updated automatically by bots/tools
Example:
- name: Setup without verification
uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
verify-lock: 'false'
Warning: Won't catch lock file drift, so use carefully.
Comparison Table¶
| Aspect | Locked Mode | Frozen Mode |
|---|---|---|
| Command | --locked |
--frozen |
| Verifies lock | ✅ Yes | ❌ No |
| Fails on drift | ✅ Yes | ❌ No |
| Speed | Slower (verification) | Faster |
| Safety | Safer | Less safe |
| Best for | CI/CD | Trusted environments |
| Default | ✅ Yes | No |
When to Use Each Mode¶
Use Locked Mode (verify-lock: 'true') when:
- ✅ Running CI/CD pipelines
- ✅ Multiple contributors updating dependencies
- ✅ You want to enforce lock file updates
- ✅ Security is important (catch unexpected changes)
Use Frozen Mode (verify-lock: 'false') when:
- ✅ Lock file is auto-updated (Dependabot, Renovate)
- ✅ Single developer project
- ✅ Speed is critical (many CI runs)
- ✅ You have other verification mechanisms
Updating Lock Files¶
Recommended workflow:
-
Update dependencies locally:
-
Commit both files:
-
CI validates:
- PR runs with
verify-lock: 'true' - ✅ Passes because lock file matches
- Reviewers see both files changed
What NOT to do:
# ❌ Don't edit only pyproject.toml without updating lock
vim pyproject.toml
git add pyproject.toml # Missing pixi.lock!
git commit -m "Update numpy"
# CI will fail with "Lock file is outdated"
Environment Architecture¶
Understanding Pixi Environments¶
Pixi uses a feature-based system to compose environments:
Features → Environments → Activation
1. Features (Dependency Groups)¶
Features define groups of related dependencies:
[tool.pixi.feature.core.dependencies]
python = ">=3.11"
numpy = ">=1.26"
[tool.pixi.feature.dev.dependencies]
pytest = ">=7.0"
black = ">=23.0"
[tool.pixi.feature.ml.dependencies]
scikit-learn = ">=1.3"
pytorch = ">=2.0"
Think of features as: - 📦 Reusable dependency sets - 🧩 Building blocks for environments - 🎯 Logical groupings (core, dev, ML, viz, etc.)
2. Environments (Feature Combinations)¶
Environments combine multiple features:
[tool.pixi.environments]
default = ["core"] # Just core
dev = ["core", "dev"] # Core + dev tools
ml = ["core", "ml"] # Core + ML packages
full = ["core", "dev", "ml"] # Everything
Think of environments as: - 🎭 Different use cases (dev, test, prod) - 🔧 Job-specific setups - 📊 Matrix test configurations
3. Activation (Runtime Selection)¶
The action installs environments and activates one:
After activation:
- ✅ python → Python from activated environment
- ✅ pytest → If in activated environment
- ✅ Package imports work from activated environment
- ❌ Packages from other environments not in PATH (but can use pixi run -e)
Environment Lifecycle¶
Step-by-step what happens:
- Validation: Check
pixi.lockexists - Installation: Install pixi (if not cached)
- Lock Check (if
verify-lock: 'true'): Verify lock matches pyproject.toml - Environment Creation: Install specified environments
- Activation: Add environment to PATH
- Verification: Log environment info
Directory Structure¶
After installation, your project looks like:
my-project/
├── pyproject.toml
├── pixi.lock
├── .pixi/ # Created by pixi
│ ├── envs/
│ │ ├── default/ # Each environment
│ │ │ ├── bin/
│ │ │ ├── lib/
│ │ │ └── ...
│ │ ├── dev/
│ │ │ ├── bin/
│ │ │ └── ...
│ │ └── ml/
│ │ └── ...
│ └── bin/
│ └── pixi # Pixi binary
├── src/
└── tests/
Caching caches: The entire .pixi/ directory
Caching Strategy¶
How Caching Works¶
Cache Key: pixi-<hash(pixi.lock)>
What's cached: Entire .pixi/ directory containing:
- All installed environments
- Pixi binary
- Package metadata
Cache flow:
┌─────────────────┐
│ Run starts │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Hash pixi.lock │ ← Generate cache key
└────────┬────────┘
│
▼
┌────────┐
│ Cache │
│ hit? │
└───┬────┘
│
┌───┴────┐
│ │
YES NO
│ │
▼ ▼
┌────────┐ ┌──────────────┐
│Restore │ │Install fresh │
│cache │ │ │
└───┬────┘ └──────┬───────┘
│ │
│ ▼
│ ┌──────────────┐
│ │ Is push to │
│ │ main branch? │
│ └──────┬───────┘
│ │
│ ┌───┴───┐
│ │ │
│ YES NO
│ │ │
│ ▼ ▼
│ ┌─────────┐ ┌────────┐
│ │ Save │ │ Skip │
│ │ cache │ │ save │
│ └─────────┘ └────────┘
│
└──────────┬──────────────┘
│
▼
┌──────────┐
│ Done │
└──────────┘
Cache Performance¶
Typical timings:
| Scenario | Time | Notes |
|---|---|---|
| No cache, small project | 1-2 min | Few packages |
| No cache, large project | 5-10 min | Many packages (ML libs) |
| Cache hit | 20-40 sec | Restore + verify |
| Cache miss | 5-10 min | Full install + save |
Factors affecting speed: - Number of packages - Package size (pytorch is large!) - Number of environments - Network speed (first install)
Cache Invalidation¶
Automatic invalidation when:
- ✅ pixi.lock changes (new hash → new key)
- ✅ GitHub clears old caches (7 days unused)
Manual invalidation:
- Clear cache via GitHub UI (Settings → Actions → Caches)
- Change cache key (modify action or add version to pyproject.toml)
Cache won't invalidate when:
- ❌ Only pyproject.toml changes (lock file unchanged)
- ❌ Code changes
- ❌ Non-dependency configuration changes
Cache Best Practices¶
DO: - ✅ Enable caching for stable projects - ✅ Use caching in production CI - ✅ Monitor cache hit rates - ✅ Include lock file in commits
DON'T: - ❌ Enable caching when debugging dependency issues - ❌ Rely on cache without lock file verification - ❌ Cache in fork PRs (limited cache space)
Optimal configuration:
# Main branch - write cache
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true'
verify-lock: 'true' # Verify even with cache
# PR branches - read cache only (automatic)
# Same configuration, but cache-write is disabled automatically
Testing Guide¶
This action is comprehensively tested across multiple scenarios to ensure reliability. Reference the test workflow at .github/workflows/test-python-setup-pixi.yml.
Test Coverage Matrix¶
| Test Job | Purpose | Key Validations | Fixture |
|---|---|---|---|
test-pixi-basic |
Default behavior | Pixi and Python installed, packages available | test-pixi-basic/ |
test-pixi-default-environment |
Default environment only | Only default environment installed | test-pixi-default-environment/ |
test-pixi-multiple-environments |
Multiple environments | Multiple envs installed, correct one activated | test-pixi-multiple-environments/ |
test-pixi-environment-activation |
Environment activation | Specific environment activated correctly | test-pixi-environment-activation/ |
test-pixi-lock-verification-valid |
Valid lock file | Lock verification passes | test-pixi-lock-verification-valid/ |
test-pixi-lock-verification-outdated |
Outdated lock handling | Fails with outdated lock file | test-pixi-lock-verification-outdated/ |
test-pixi-lock-verification-disabled |
Skip lock check | Works with verify-lock: 'false' |
test-pixi-lock-verification-disabled/ |
test-pixi-lock-verification-validation |
Missing lock file | Fails without lock file | test-pixi-lock-verification-validation/ |
test-pixi-caching-populate |
Cache population | Cache created and packages installed | test-pixi-caching-populate/ |
test-pixi-caching-retrieve |
Cache retrieval | Cache restored on subsequent runs | test-pixi-caching-retrieve/ |
test-pixi-caching-validation |
Cache without lock | Fails when cache enabled without lock | - |
test-pixi-matrix |
Cross-platform matrix | Works on Linux/Windows/macOS with multiple environments | test-pixi-matrix/ |
test-pixi-error-scenarios |
Error handling | Proper errors for missing lock, wrong environment | test-pixi-basic/ |
test-pixi-complex-project |
Complex setup | Multiple features and environments | test-pixi-complex-project/ |
Test Fixtures Location¶
All test fixtures are located in tests/data/pixi/ directory:
tests/data/pixi/
├── test-pixi-basic/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-default-environment/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-multiple-environments/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-environment-activation/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-lock-verification-valid/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-lock-verification-outdated/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-lock-verification-disabled/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-lock-verification-validation/
│ └── pyproject.toml # No lock file (intentional)
├── test-pixi-caching-populate/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-caching-retrieve/
│ ├── pyproject.toml
│ └── pixi.lock
├── test-pixi-matrix/
│ ├── pyproject.toml
│ └── pixi.lock
└── test-pixi-complex-project/
├── pyproject.toml
└── pixi.lock
Example Test Fixtures¶
test-pixi-basic/pyproject.toml:
[project]
name = "test-pixi-basic"
version = "0.1.0"
dependencies = []
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"]
[tool.pixi.dependencies]
python = ">=3.11"
numpy = ">=1.26"
pytest = ">=7.0"
test-pixi-multiple-environments/pyproject.toml:
[project]
name = "test-multi-env"
version = "0.1.0"
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"]
[tool.pixi.feature.core.dependencies]
python = ">=3.11"
numpy = ">=1.26"
[tool.pixi.feature.py311.dependencies]
python = "3.11.*"
pandas = ">=2.0"
[tool.pixi.feature.py312.dependencies]
python = "3.12.*"
pandas = ">=2.0"
[tool.pixi.environments]
default = ["core"]
py311 = ["core", "py311"]
py312 = ["core", "py312"]
test-pixi-complex-project/pyproject.toml:
[project]
name = "test-complex"
version = "0.1.0"
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "osx-64", "osx-arm64", "win-64"]
[tool.pixi.feature.core.dependencies]
python = ">=3.11"
requests = ">=2.31"
[tool.pixi.feature.ml.dependencies]
numpy = ">=1.26"
scikit-learn = ">=1.3"
[tool.pixi.feature.dev.dependencies]
pytest = ">=7.0"
black = ">=23.0"
[tool.pixi.environments]
default = ["core", "dev"]
ml = ["core", "ml", "dev"]
prod = ["core", "ml"]
Basic Test Setup¶
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'false'
- run: pytest tests/
Matrix Testing: Multiple Python Versions¶
jobs:
test:
strategy:
matrix:
python-env: ['py310', 'py311', 'py312']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: ${{ matrix.python-env }}
activate-environment: ${{ matrix.python-env }}
cache: 'false'
- run: pytest
Matrix Testing: Multiple Operating Systems¶
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'false'
- run: pytest
Matrix Testing: Combined¶
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-env: ['py311', 'py312']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: ${{ matrix.python-env }}
activate-environment: ${{ matrix.python-env }}
cache: 'false'
- run: pytest
Testing Multiple Environments in One Job¶
jobs:
test-all:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
environments: 'dev test'
activate-environment: 'dev'
cache: 'false'
- name: Lint with dev environment
run: |
black --check .
ruff check .
- name: Test with test environment
run: pixi run -e test pytest --cov
Lock File Verification Tests¶
Test Valid Lock File¶
jobs:
test-valid-lock:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
verify-lock: 'true' # Should pass
cache: 'false'
- run: pytest
Test Outdated Lock File (Should Fail)¶
jobs:
test-outdated-lock:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
# Modify pyproject.toml to make lock outdated
- run: |
echo "" >> pyproject.toml
echo "[tool.pixi.dependencies]" >> pyproject.toml
echo "newpackage = \">=1.0\"" >> pyproject.toml
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
id: setup
continue-on-error: true
with:
verify-lock: 'true' # Should fail
cache: 'false'
- name: Verify it failed
if: steps.setup.outcome != 'failure'
run: |
echo "ERROR: Should have failed with outdated lock"
exit 1
Test Without Lock File (Should Fail)¶
jobs:
test-no-lock:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: rm pixi.lock # Remove lock file
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
id: setup
continue-on-error: true
with:
cache: 'false'
- name: Verify it failed
if: steps.setup.outcome != 'failure'
run: |
echo "ERROR: Should have failed without lock file"
exit 1
Caching Tests¶
Test Cache Population¶
jobs:
cache-populate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true'
- run: pytest
Test Cache Retrieval¶
jobs:
cache-populate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true'
- run: pytest
cache-retrieve:
runs-on: ubuntu-latest
needs: cache-populate
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
id: setup
with:
cache: 'true'
# Second run should be much faster
- run: pytest
Troubleshooting¶
Common Issues¶
Issue: "pixi.lock file does not exist"¶
Error message:
❌ pixi.lock file does not exist!
To fix this issue:
Run 'pixi install' locally to generate pixi.lock and commit it to your repository
Solution:
Issue: "Lock file is outdated"¶
Error message:
Cause: You modified pyproject.toml without updating the lock file.
Solution:
# Update lock file
pixi install
# Commit both files
git add pyproject.toml pixi.lock
git commit -m "Update dependencies"
git push
Issue: Cache not working¶
Symptoms: Every run takes 5-10 minutes even with cache: 'true'
Possible causes:
-
Lock file keeps changing:
-
Running on PR from fork:
- Forks have limited cache access
-
This is a GitHub limitation
-
Cache was cleared:
- Check GitHub Actions → Caches
- Old caches are auto-deleted after 7 days
Solution:
- Ensure pixi.lock is committed
- Check cache hit/miss in logs
- For fork PRs, accept slower runs
Issue: "Environment not found"¶
Error message:
Cause: Trying to activate an environment that doesn't exist in pyproject.toml or wasn't installed.
Solution:
# Wrong
with:
environments: 'dev'
activate-environment: 'ml' # ❌ ml not installed!
# Correct
with:
environments: 'dev ml' # ✅ Install both
activate-environment: 'ml' # ✅ Now can activate ml
Issue: Package not found after setup¶
Symptoms: ModuleNotFoundError: No module named 'numpy' even though it's in pyproject.toml
Possible causes:
-
Wrong environment activated:
-
Package in wrong feature: Check your
pyproject.tomlenvironment definitions
Solution:
# Check which environment has the package
pixi list -e dev | grep numpy
pixi list -e ml | grep numpy
# Either switch environment or add package to current environment
Issue: Different versions locally vs CI¶
Symptoms: Works locally but fails in CI (or vice versa)
Cause: Lock file not synchronized
Solution:
# Regenerate lock file locally
rm pixi.lock
pixi install
# Commit and push
git add pixi.lock
git commit -m "Regenerate lock file"
git push
Issue: Windows-specific path issues¶
Symptoms: Works on Linux/Mac but fails on Windows
Cause: Path separators or shell-specific commands
Solution:
# Use pixi tasks instead of shell commands
# In pyproject.toml:
[tool.pixi.tasks]
test = "pytest tests/"
# In workflow:
- run: pixi run test # Cross-platform!
Issue: Slow installation even with cache¶
Symptoms: Cache hits but still takes 2-3 minutes
Cause: Lock verification and environment activation take time
Solution:
# For trusted environments, disable verification
with:
cache: 'true'
verify-lock: 'false' # Faster but less safe
Debug Mode¶
To get more information about what's happening:
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'false'
# Add debug steps
- name: Debug environment info
run: |
echo "Pixi version:"
pixi --version
echo "Available environments:"
pixi info
echo "Active Python:"
which python
python --version
echo "Installed packages:"
pixi list
echo "PATH:"
echo $PATH
Best Practices¶
1. Always Commit Lock Files¶
✅ DO:
❌ DON'T:
2. Use Locked Mode in CI¶
✅ DO:
❌ DON'T:
Exception: Disable when lock is auto-updated by bots
3. Enable Caching for Stable Projects¶
✅ DO:
❌ DON'T:
4. Use Minimal Environments¶
✅ DO:
# Install only what you need
jobs:
lint:
steps:
- uses: .../pixi@v1
with:
environments: 'dev' # Just linting tools
test:
steps:
- uses: .../pixi@v1
with:
environments: 'test' # Just test deps
❌ DON'T:
# Install everything everywhere
jobs:
lint:
steps:
- uses: .../pixi@v1
with:
environments: 'dev test ml viz' # Overkill!
5. One Environment Activation Per Job¶
✅ DO:
- uses: .../pixi@v1
with:
environments: 'dev'
activate-environment: 'dev'
# All subsequent steps use dev environment
❌ DON'T:
# Trying to activate multiple environments (not possible)
- uses: .../pixi@v1
with:
activate-environment: 'dev'
- uses: .../pixi@v1 # ❌ Second activation
with:
activate-environment: 'test'
If you need multiple environments, use pixi run -e:
- uses: .../pixi@v1
with:
environments: 'dev test'
activate-environment: 'dev'
- run: black --check . # Uses dev
- run: pixi run -e test pytest # Uses test
6. Organize Dependencies by Purpose¶
✅ DO:
[tool.pixi.feature.core.dependencies]
# Core runtime dependencies
numpy = ">=1.26"
pandas = ">=2.0"
[tool.pixi.feature.dev.dependencies]
# Development tools
black = ">=23.0"
ruff = ">=0.1"
[tool.pixi.feature.test.dependencies]
# Testing tools
pytest = ">=7.0"
pytest-cov = ">=4.0"
[tool.pixi.environments]
default = ["core"]
dev = ["core", "dev"]
ci = ["core", "test"]
❌ DON'T:
[tool.pixi.dependencies]
# Everything in one place
numpy = ">=1.26"
pandas = ">=2.0"
black = ">=23.0"
pytest = ">=7.0"
# No organization!
7. Use Descriptive Environment Names¶
✅ DO:
[tool.pixi.environments]
default = ["core"]
dev = ["core", "dev-tools"]
test-py311 = ["core", "test", "py311"]
test-py312 = ["core", "test", "py312"]
prod = ["core"]
❌ DON'T:
8. Document Environment Purpose¶
✅ DO:
[tool.pixi.environments]
# Core dependencies only - minimal install
default = ["core"]
# Development environment - includes linting and formatting tools
dev = ["core", "dev-tools"]
# CI testing environment - includes test runners and coverage
ci = ["core", "test"]
# Production environment - runtime dependencies only
prod = ["core"]
9. Pin Major Python Version¶
✅ DO:
❌ DON'T:
10. Test Lock File Verification in CI¶
✅ DO:
jobs:
validate-lock:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: .../pixi@v1
with:
verify-lock: 'true' # Enforce verification
cache: 'false'
This catches PRs with outdated lock files early.
Migration Guide¶
From setup-python + pip¶
Before (pip):
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- run: pip install -r requirements.txt
After (pixi):
Migration steps:
1. Create pyproject.toml with pixi configuration
2. Run pixi install locally to generate pixi.lock
3. Commit both files
4. Update workflow to use pixi action
From setup-python + conda¶
Before (conda):
- uses: conda-incubator/setup-miniconda@v3
with:
python-version: '3.11'
environment-file: environment.yml
After (pixi):
Migration steps:
1. Convert environment.yml to pyproject.toml pixi format
2. Run pixi install to generate lock file
3. Commit files
4. Update workflow
From mamba-org/setup-micromamba¶
Before (micromamba):
- uses: mamba-org/setup-micromamba@v1
with:
environment-file: environment.yml
cache-environment: true
After (pixi):
Pixi is faster than micromamba and has better lock file support!
Additional Resources¶
- Pixi Documentation: https://prefix.dev/docs/pixi
- Pixi GitHub: https://github.com/prefix-dev/pixi
- Conda-forge: https://conda-forge.org/
- Action Source:
serapeum-org/github-actions/actions/python-setup/pixi
Examples Repository¶
See the test suite for comprehensive examples:
- .github/workflows/test-python-setup-pixi.yml - All test scenarios
- tests/data/pixi/ - Example configurations
Support¶
For issues or questions: 1. Check Troubleshooting 2. Review test examples 3. Open an issue in the repository
Version: 1.0.0
Last Updated: January 2026
Compatibility: Linux, macOS, Windows
Pixi Version: v0.49.0