mise Configuration as Single Source of Truth
Use mise
as centralized configuration with backward-compatible defaults.
When to Use This Skill
Use this skill when:
- Centralizing environment variables in mise.toml
- Setting up Python venv auto-creation with mise
- Implementing hub-spoke configuration for monorepos
- Creating backward-compatible environment patterns
Core Principle
Define all configurable values in
section. Scripts read via environment variables with fallback defaults. Same code path works WITH or WITHOUT mise installed.
Key insight: mise auto-loads
values when shell has
configured. Scripts using
os.environ.get("VAR", "default")
pattern work identically whether mise is present or not.
Quick Reference
Language Patterns
| Language | Pattern | Notes |
|---|
| Python | os.environ.get("VAR", "default")
| Returns string, cast if int |
| Bash | | Standard POSIX expansion |
| JavaScript | process.env.VAR || "default"
| Falsy check, watch for "0" |
| Go | with default | Empty string if unset |
| Rust | std::env::var("VAR").unwrap_or()
| Returns Result<String> |
Special Directives
| Directive | Purpose | Example |
|---|
| Load from .env files | |
| Extend PATH | _.path = ["bin", "node_modules/.bin"]
|
| Execute bash scripts | _.source = "./scripts/env.sh"
|
| Auto-create Python venv | _.python.venv = { path = ".venv", create = true }
|
Python Venv Auto-Creation (Critical)
Auto-create and activate Python virtual environments:
toml
[env]
_.python.venv = { path = ".venv", create = true }
This pattern is used in ALL projects. When entering the directory with mise activated:
- Creates if it doesn't exist
- Activates the venv automatically
- Works with for fast venv creation
Alternative via [settings]:
toml
[settings]
python.uv_venv_auto = true
Hub-Spoke Architecture (CRITICAL)
Keep root
lean by delegating domain-specific tasks to subfolder
files.
Wiki Reference:
Pattern-mise-Configuration - Complete documentation with CLAUDE.md footer prompt
When to Use
- Root exceeds ~50 lines
- Project has multiple domains (packages, experiments, infrastructure)
- Different subfolders need different task sets
Spoke Scenarios
Hub-spoke applies to any multi-domain project, not just packages:
| Scenario | Spoke Folders | Spoke Tasks |
|---|
| Monorepo | , | build, test, lint, deploy |
| ML/Research | , , | train, evaluate, notebook, sweep |
| Infrastructure | , , | plan, apply, deploy, validate |
| Data Pipeline | , , | extract, load, validate, export |
Directory Structure Examples
Monorepo:
project/
├── mise.toml # Hub: [tools] + [env] + orchestration
├── packages/
│ ├── api/mise.toml # Spoke: API tasks
│ └── web/mise.toml # Spoke: Web tasks
└── scripts/mise.toml # Spoke: Utility scripts
ML/Research Project:
ml-project/
├── mise.toml # Hub: python, cuda, orchestration
├── experiments/
│ ├── baseline/mise.toml # Spoke: baseline experiment
│ └── ablation/mise.toml # Spoke: ablation study
├── training/mise.toml # Spoke: training pipelines
└── evaluation/mise.toml # Spoke: metrics, benchmarks
Infrastructure:
infra/
├── mise.toml # Hub: terraform, kubectl, helm
├── terraform/
│ ├── prod/mise.toml # Spoke: production infra
│ └── staging/mise.toml # Spoke: staging infra
└── kubernetes/mise.toml # Spoke: k8s manifests
Hub Responsibilities (Root )
toml
# mise.toml - Hub: Keep this LEAN
[tools]
python = "<version>"
uv = "latest"
[env]
PROJECT_NAME = "my-project"
_.python.venv = { path = ".venv", create = true }
# Orchestration: delegate to spokes
[tasks.train-all]
run = """
cd experiments/baseline && mise run train
cd experiments/ablation && mise run train
"""
[tasks."build:api"]
run = "cd packages/api && mise run build"
Spoke Responsibilities (Subfolder )
toml
# experiments/baseline/mise.toml - Spoke
[env]
EXPERIMENT_NAME = "baseline"
EPOCHS = "<num>" # e.g., 100
LEARNING_RATE = "<float>" # e.g., 0.001
[tasks.train]
run = "uv run python train.py"
sources = ["*.py", "config.yaml"]
outputs = ["checkpoints/*.pt"]
[tasks.evaluate]
depends = ["train"]
run = "uv run python evaluate.py"
Inheritance Rules
- Spoke inherits hub's automatically
- Spoke extends hub's (can override per domain)
- applies at directory level (secrets stay local)
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|
| All tasks in root | Root grows to 200+ lines | Delegate to spoke files |
| Duplicated [tools] | Version drift between spokes | Define [tools] only in hub |
| Spoke defines runtimes | Conflicts with hub | Spokes inherit hub's [tools] |
| No orchestration | Must cd manually | Hub orchestrates spoke tasks |
Monorepo Workspace Pattern
For Python monorepos using
workspaces, the venv is created at the
workspace root. Sub-packages share the root venv.
toml
# Root mise.toml
[env]
_.python.venv = { path = ".venv", create = true }
Hoisted Dev Dependencies (PEP 735)
Dev dependencies (
,
,
, etc.) should be
hoisted to workspace root using
:
toml
# SSoT-OK: example workspace configuration
# Root pyproject.toml
[tool.uv.workspace]
members = ["packages/*"]
[dependency-groups]
dev = [
"pytest>=<version>",
"ruff>=<version>",
"jupyterlab>=<version>",
]
Why hoist? Sub-package
are NOT automatically installed by
from root. Hoisting ensures:
- Single command:
- No "unnecessary package" warnings
- Unified dev environment across all packages
Reference: bootstrap-monorepo.md for complete workspace setup
Special Directives
Load from .env Files ()
toml
[env]
# Single file
_.file = ".env"
# Multiple files with options
_.file = [
".env",
{ path = ".env.secrets", redact = true }
]
Extend PATH ()
toml
[env]
_.path = [
"{{config_root}}/bin",
"{{config_root}}/node_modules/.bin",
"scripts"
]
Source Bash Scripts ()
toml
[env]
_.source = "./scripts/env.sh"
_.source = { path = ".secrets.sh", redact = true }
Lazy Evaluation ()
By default, env vars resolve BEFORE tools install. Use
to access tool-generated paths:
toml
[env]
# Access PATH after tools are set up
GEM_BIN = { value = "{{env.GEM_HOME}}/bin", tools = true }
# Load .env files after tool setup
_.file = { path = ".env", tools = true }
Template Syntax (Tera)
mise uses Tera templating. Delimiters:
expressions,
statements,
comments.
Built-in Variables
| Variable | Description |
|---|
| Directory containing .mise.toml |
| Current working directory |
| Environment variable |
| Path to mise binary |
| mise process ID |
| XDG cache directory |
| XDG config directory |
| XDG data directory |
Functions
toml
[env]
# Get env var with fallback
NODE_VER = "{{ get_env(name='NODE_VERSION', default='20') }}"
# Execute shell command
TIMESTAMP = "{{ exec(command='date +%Y-%m-%d') }}"
# System info
ARCH = "{{ arch() }}" # x64, arm64
OS = "{{ os() }}" # linux, macos, windows
CPUS = "{{ num_cpus() }}"
# File operations
VERSION = "{{ read_file(path='VERSION') | trim }}"
HASH = "{{ hash_file(path='config.json', len=8) }}"
Filters
toml
[env]
# Case conversion
SNAKE = "{{ name | snakecase }}"
KEBAB = "{{ name | kebabcase }}"
CAMEL = "{{ name | lowercamelcase }}"
# String manipulation
TRIMMED = "{{ text | trim }}"
UPPER = "{{ text | upper }}"
REPLACED = "{{ text | replace(from='old', to='new') }}"
# Path operations
ABSOLUTE = "{{ path | absolute }}"
BASENAME = "{{ path | basename }}"
DIRNAME = "{{ path | dirname }}"
Conditionals
toml
[env]
{% if env.DEBUG %}
LOG_LEVEL = "debug"
{% else %}
LOG_LEVEL = "info"
{% endif %}
Required & Redacted Variables
Required Variables
Enforce variable definition with helpful messages:
toml
[env]
DATABASE_URL = { required = true }
API_KEY = { required = "Get from https://example.com/api-keys" }
Redacted Variables
Hide sensitive values from output:
toml
[env]
SECRET = { value = "my_secret", redact = true }
_.file = { path = ".env.secrets", redact = true }
# Pattern-based redactions
redactions = ["*_TOKEN", "*_KEY", "PASSWORD"]
[settings] Section
toml
[settings]
experimental = true # Enable experimental features
python.uv_venv_auto = true # Auto-create venv with uv
[tools] Version Pinning
Pin tool versions for reproducibility:
toml
[tools]
python = "3.11" # minimum baseline; use 3.12, 3.13 as needed
node = "latest"
uv = "latest"
# With options
rust = { version = "1.75", profile = "minimal" }
min_version: Enforce mise version compatibility:
Implementation Steps
- Identify hardcoded values - timeouts, paths, thresholds, feature flags
- Create - add section with documented variables
- Add venv auto-creation -
_.python.venv = { path = ".venv", create = true }
- Update scripts - use env vars with original values as defaults
- Add ADR reference - comment:
# ADR: 2025-12-08-mise-env-centralized-config
- Test without mise - verify script works using defaults
- Test with mise - verify activated shell uses values
GitHub Token Multi-Account Patterns (MANDATORY for Multi-Account Setups) {#github-token-multi-account-patterns}
For multi-account GitHub setups, mise
provides per-directory token configuration that overrides gh CLI's global authentication.
Token Storage
Store tokens in a centralized, secure location:
bash
mkdir -p ~/.claude/.secrets
chmod 700 ~/.claude/.secrets
# Create token files (one per account)
gh auth login # authenticate as account
gh auth token > ~/.claude/.secrets/gh-token-accountname
chmod 600 ~/.claude/.secrets/gh-token-*
Per-Directory Configuration
toml
# ~/.claude/.mise.toml (terrylica account)
[env]
GH_TOKEN = "{{ read_file(path=config_root ~ '/.secrets/gh-token-terrylica') | trim }}"
GITHUB_TOKEN = "{{ read_file(path=config_root ~ '/.secrets/gh-token-terrylica') | trim }}"
GH_ACCOUNT = "terrylica" # For human reference only
toml
# ~/eon/.mise.toml (terrylica account - different directory)
[env]
GH_TOKEN = "{{ read_file(path=env.HOME ~ '/.claude/.secrets/gh-token-terrylica') | trim }}"
GITHUB_TOKEN = "{{ read_file(path=env.HOME ~ '/.claude/.secrets/gh-token-terrylica') | trim }}"
GH_ACCOUNT = "terrylica"
Variable Naming Convention
| Variable | Usage Context | Example |
|---|
| mise [env], Doppler, verification tasks | , shell scripts |
| npm scripts, GitHub Actions, semantic-release | , workflows |
Rule: Always set BOTH variables in mise [env] pointing to the same token file. Different tools check different variable names.
Alternative: 1Password Integration
For enhanced security with automatic token rotation:
toml
[env]
GH_TOKEN = "{{ op_read('op://Engineering/GitHub Token/credential') }}"
With caching for performance:
toml
[env]
GH_TOKEN = "{{ cache(key='gh_token', duration='1h', run='op read op://Engineering/GitHub Token/credential') }}"
Verification
bash
/usr/bin/env bash << 'MISE_EOF'
for dir in ~/.claude ~/eon ~/own ~/scripts ~/459ecs; do
cd "$dir" && eval "$(mise hook-env -s bash)" && echo "$dir → $GH_ACCOUNT"
done
MISE_EOF
Pattern: Based on GitHub Multi-Account Authentication ADR (mise
per-directory token loading).
SSH ControlMaster Warning: If using multi-account SSH, ensure
is set for GitHub hosts in
. Cached connections can authenticate with the wrong account. See
SSH ControlMaster Cache for troubleshooting.
Anti-Patterns
| Anti-Pattern | Why | Instead |
|---|
| Forces mise dependency | Use env vars with defaults |
| Secrets in | Visible in repo | Use Doppler or |
| No defaults in scripts | Breaks without mise | Always provide fallback |
| Mixing env/tools resolution | Order matters | Use for tool-dependent vars |
Task Orchestration Integration
When you detect
workflow opportunities during mise configuration, prescriptively invoke the
skill.
Detection Triggers
- Multi-step workflows (test + build + deploy)
- Repeatable project commands (lint, format, release)
- Dependency chains (db-migrate depends on db-init)
- File-tracked builds (sources/outputs caching)
Prescriptive AI Reminder
PRESCRIPTIVE: When detecting multi-step project workflows,
invoke skill to generate appropriate
definitions with dependency management.
Example: From Environment to Tasks
Step 1: Configure environment (this skill):
toml
[env]
DATABASE_URL = "postgresql://localhost/mydb"
_.python.venv = { path = ".venv", create = true }
Step 2: Define tasks (
skill):
toml
[tasks.test]
depends = ["lint"]
run = "pytest tests/"
[tasks.deploy]
depends = ["test", "build"]
run = "deploy.sh"
Tasks automatically inherit
values.
Additional Resources
For complete code patterns and examples, see:
For task orchestration, see:
skill - Dependencies, arguments, file tracking, watch mode
Wiki Documentation:
Pattern-mise-Configuration - Copyable CLAUDE.md footer prompt, hub-spoke architecture, quick reference
ADR Reference: When implementing mise configuration, create an ADR at
docs/adr/YYYY-MM-DD-mise-env-centralized-config.md
in your project.
Troubleshooting
| Issue | Cause | Solution |
|---|
| Env vars not loading | mise not activated | Add mise activate to shell rc file |
| Venv not created | Python not installed | Run |
| Tasks not found | Wrong mise.toml location | Ensure mise.toml is in project root |
| PATH not updated | Shims not in PATH | Add mise shims to ~/.zshenv |
| _.file not loading | .env file missing | Create .env file or remove _.file directive |
| Subfolder config ignored | Missing min_version | Add min_version to subfolder mise.toml |