Skip to content

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

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 install --locked
- Verifies 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 install --frozen
- Uses 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:

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.

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:

  1. Lock file is mandatory: Action fails immediately if pixi.lock doesn't exist
  2. Generated locally: Developers run pixi install on their machines
  3. Committed to repository: Lock file is version-controlled
  4. 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:

  1. Update dependencies locally:

    # Edit pyproject.toml
    vim pyproject.toml
    
    # Update lock file
    pixi install
    
    # Verify it works
    pixi run pytest
    

  2. Commit both files:

    git add pyproject.toml pixi.lock
    git commit -m "Update numpy to 2.0"
    git push
    

  3. CI validates:

  4. PR runs with verify-lock: 'true'
  5. ✅ Passes because lock file matches
  6. 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:

FeaturesEnvironmentsActivation

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:

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)

Environment Lifecycle

Step-by-step what happens:

  1. Validation: Check pixi.lock exists
  2. Installation: Install pixi (if not cached)
  3. Lock Check (if verify-lock: 'true'): Verify lock matches pyproject.toml
  4. Environment Creation: Install specified environments
    pixi install --locked  # or --frozen
    # Creates .pixi/envs/dev/, .pixi/envs/ml/, etc.
    
  5. Activation: Add environment to PATH
    # Adds .pixi/envs/dev/bin to PATH
    
  6. 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:

# On your local machine
pixi install
git add pixi.lock
git commit -m "Add pixi lock file"
git push

Issue: "Lock file is outdated"

Error 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 push

Issue: Cache not working

Symptoms: Every run takes 5-10 minutes even with cache: 'true'

Possible causes:

  1. Lock file keeps changing:

    # Check if lock file is in .gitignore
    git check-ignore pixi.lock
    # Should output nothing (not ignored)
    

  2. Running on PR from fork:

  3. Forks have limited cache access
  4. This is a GitHub limitation

  5. Cache was cleared:

  6. Check GitHub Actions → Caches
  7. 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:

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 ml

Issue: Package not found after setup

Symptoms: ModuleNotFoundError: No module named 'numpy' even though it's in pyproject.toml

Possible causes:

  1. 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
    

  2. Package in wrong feature: Check your pyproject.toml environment 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:

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!

2. Use Locked Mode in CI

DO:

with:
  verify-lock: 'true'  # Catch drift

DON'T:

with:
  verify-lock: 'false'  # Silent failures

Exception: Disable when lock is auto-updated by bots

3. Enable Caching for Stable Projects

DO:

# Stable project with infrequent dependency changes
with:
  cache: 'true'

DON'T:

# When debugging dependency issues
with:
  cache: 'true'  # Cache might mask issues

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:

[tool.pixi.environments]
env1 = ["core"]
env2 = ["core", "dev"]
myenv = ["core", "test"]

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:

[tool.pixi.dependencies]
python = ">=3.11,<3.13"  # Explicit range

DON'T:

[tool.pixi.dependencies]
python = ">=3.11"  # Too permissive

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):

- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
  with:
    cache: 'true'

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):

- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
  with:
    cache: 'true'

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):

- uses: serapeum-org/github-actions/actions/python-setup/pixi@v1
  with:
    cache: 'true'

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