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.
- Overview
- Quick Start
- Inputs Reference
- Features
- Usage Scenarios
- Lock File Management
- Environment Architecture
- Caching Strategy
- Testing Guide
- Troubleshooting
- Best Practices
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
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: pyteststeps:
- 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 environmentsteps:
- 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!| 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' |
version |
Version of pixi to install | No | 'latest' |
Release tag (e.g., 'v0.65.0') or 'latest' |
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'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
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
.pixidirectory based onpixi.lockhash - Cache is only written on
pushevents to themainbranch - Significantly speeds up subsequent CI runs (from minutes to seconds)
- Requires
pixi.lockfile - 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'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.lockmatchespyproject.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'Selects the pixi release installed by prefix-dev/setup-pixi (forwarded as its pixi-version input).
Default ('latest'): Installs the newest pixi release.
Pin a specific release tag for reproducible runs, or keep 'latest' for the newest release:
# Pin a specific pixi version (recommended for reproducibility)
version: 'v0.65.0'
# Always use the newest pixi release
version: 'latest'Note: Pass the tag exactly as published by pixi, including the leading v (e.g. 'v0.65.0'). The pinned
version must be able to read your committed pixi.lock format.
Breaking change (pixi/v1): Earlier releases of this action always installed a pinned pixi v0.65.0. It now
defaults to version: 'latest', so existing pixi/v1 consumers that do not set version will start getting the
newest pixi resolved against their committed pixi.lock. To keep the previous behavior, pin version: 'v0.65.0'.
The action always requires a pixi.lock file to be present in your repository. This ensures reproducible builds across all environments.
What happens:
- ✅ Checks if
pixi.lockexists - ❌ Fails with a clear error message if missing
- 📝 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
The action supports two installation modes controlled by the verify-lock input:
Locked Mode (verify-lock: 'true' - default):
pixi install --locked- Verifies
pixi.lockmatchespyproject.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 install --frozen- Uses
pixi.lockwithout 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 |
After installation, the action automatically logs environment information:
Pixi Information:
Active environment: dev
Available environments:
- default
- dev
- test
- prod
Installed Packages:
Installed packages summary:
python 3.11.7
numpy 1.26.2
pandas 2.1.4
pytest 7.4.3
...
This appears in collapsible sections in GitHub Actions logs for easy debugging.
When cache: 'true' is enabled:
Cache Behavior:
- Key:
pixi-<hash(pixi.lock)> - Cached:
.pixi/directory (all environments) - Write: Only on
pushtomainbranch - 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.lockchanges - Manual: bump version in
pyproject.tomlor clear GitHub cache
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).
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.13Scenario: 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.
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.
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 reportResult: Multiple environments available, explicit control over which is active.
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
pytestResult: Tests run on 6 combinations (3 OS × 2 Python versions), each with correct environment.
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.pyResult: Different jobs use optimized environments with only necessary dependencies.
Scenario: Speed up CI by caching the pixi environment.
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: pytestTime: ~5-10 minutes per run (full package installation each time)
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: pytestTime:
- 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
pushtomainbranch - 🔄 Updates cache: When
pixi.lockchanges
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 pushThe 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
The verify-lock input controls how pixi uses the lock file:
Command used: pixi install --locked
Behavior:
- Reads
pixi.lock - Compares with
pyproject.toml - ❌ Fails if mismatch (dependencies added/changed without updating lock)
- ✅ 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' # DefaultWhat 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!"Command used: pixi install --frozen
Behavior:
- Reads
pixi.lock - Installs exact versions from lock file
- ✅ No verification - trusts lock file is correct
- 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.
| 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 |
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
Recommended workflow:
-
Update dependencies locally:
# Edit pyproject.toml vim pyproject.toml # Update lock file pixi install # Verify it works pixi run pytest
-
Commit both files:
git add pyproject.toml pixi.lock git commit -m "Update numpy to 2.0" git push -
CI validates:
- PR runs with
verify-lock: 'true' - ✅ Passes because lock file matches
- Reviewers see both files changed
- PR runs with
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"Pixi uses a feature-based system to compose environments:
Features → Environments → Activation
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.)
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"] # EverythingThink of environments as:
- 🎭 Different use cases (dev, test, prod)
- 🔧 Job-specific setups
- 📊 Matrix test configurations
The action installs environments and activates one:
environments: 'dev ml' # Installs both
activate-environment: 'dev' # Activates dev (adds to PATH)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)
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
pixi install --locked # or --frozen # Creates .pixi/envs/dev/, .pixi/envs/ml/, etc.
- Activation: Add environment to PATH
# Adds .pixi/envs/dev/bin to PATH - Verification: Log environment info
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
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 │
└──────────┘
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)
Automatic invalidation when:
- ✅
pixi.lockchanges (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.tomlchanges (lock file unchanged) - ❌ Code changes
- ❌ Non-dependency configuration changes
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 automaticallyThis action is comprehensively tested across multiple scenarios to ensure reliability. Reference the test workflow at .github/workflows/test-python-setup-pixi.yml.
| 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/ |
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
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"]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/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: pytestjobs:
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: pytestjobs:
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: pytestjobs:
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 --covjobs:
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: pytestjobs:
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 1jobs:
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 1jobs:
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: pytestjobs:
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: pytestError 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:
# On your local machine
pixi install
git add pixi.lock
git commit -m "Add pixi lock file"
git pushError message:
The pixi.lock file is outdated. Please run `pixi install` to update it.
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 pushSymptoms: Every run takes 5-10 minutes even with cache: 'true'
Possible causes:
-
Lock file keeps changing:
# Check if lock file is in .gitignore git check-ignore pixi.lock # Should output nothing (not ignored)
-
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.lockis committed - Check cache hit/miss in logs
- For fork PRs, accept slower runs
Error message:
Error: Environment 'ml' not found
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 mlSymptoms: ModuleNotFoundError: No module named 'numpy' even though it's in pyproject.toml
Possible causes:
-
Wrong environment activated:
# Package is in 'ml' environment but 'dev' is active with: environments: 'dev ml' activate-environment: 'dev' # numpy might not be here - run: python -c "import numpy" # ❌ Fails
-
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 environmentSymptoms: 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 pushSymptoms: 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!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 safeTo 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✅ DO:
git add pyproject.toml pixi.lock
git commit -m "Update numpy to 2.0"❌ DON'T:
git add pyproject.toml
# Missing pixi.lock - CI will fail!✅ DO:
with:
verify-lock: 'true' # Catch drift❌ DON'T:
with:
verify-lock: 'false' # Silent failuresException: Disable when lock is auto-updated by bots
✅ DO:
# Stable project with infrequent dependency changes
with:
cache: 'true'❌ DON'T:
# When debugging dependency issues
with:
cache: 'true' # Cache might mask issues✅ 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!✅ 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✅ 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!✅ DO:
[tool.pixi.environments]
default = ["core"]
dev = ["core", "dev-tools"]
test-py311 = ["core", "test", "py311"]
test-py312 = ["core", "test", "py312"]
prod = ["core"]❌ DON'T:
[tool.pixi.environments]
env1 = ["core"]
env2 = ["core", "dev"]
myenv = ["core", "test"]✅ 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"]✅ DO:
[tool.pixi.dependencies]
python = ">=3.11,<3.13" # Explicit range❌ DON'T:
[tool.pixi.dependencies]
python = ">=3.11" # Too permissive✅ 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.
Before (pip):
- uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- run: pip install -r requirements.txtAfter (pixi):
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true'Migration steps:
- Create
pyproject.tomlwith pixi configuration - Run
pixi installlocally to generatepixi.lock - Commit both files
- Update workflow to use pixi action
Before (conda):
- uses: conda-incubator/setup-miniconda@v3
with:
python-version: '3.11'
environment-file: environment.ymlAfter (pixi):
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true'Migration steps:
- Convert
environment.ymltopyproject.tomlpixi format - Run
pixi installto generate lock file - Commit files
- Update workflow
Before (micromamba):
- uses: mamba-org/setup-micromamba@v1
with:
environment-file: environment.yml
cache-environment: trueAfter (pixi):
- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
with:
cache: 'true'Pixi is faster than micromamba and has better lock file support!
- 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
See the test suite for comprehensive examples:
.github/workflows/test-python-setup-pixi.yml- All test scenariostests/data/pixi/- Example configurations
For issues or questions:
- Check Troubleshooting
- Review test examples
- Open an issue in the repository
Version: 1.0.0
Last Updated: January 2026
Compatibility: Linux, macOS, Windows
Pixi Version: v0.49.0