Releases¶
This project is installed by cloning. Releases are git tags on main. Docker
images are built locally with build.sh — no image registry publishing. The
version is authoritative from the latest git tag (not pyproject.toml).
Development model: trunk-based¶
Everyone works on short-lived branches off main. Changes land via pull requests.
git checkout -b feat/entrypoint-seed main
# ... commit, push ...
# Open PR. CI runs lint + test.
# Squash-merge to main.
Branch naming¶
| Prefix | Use for |
|---|---|
feat/ |
New features |
fix/ |
Bug fixes |
chore/ |
Maintenance (deps, CI, docs) |
No develop, release, or hotfix branches — main is the only integration
branch. Hotfixes follow the same PR flow from main.
Commit messages¶
Use the conventional-commit prefix pattern for clarity (not enforced by a hook):
feat: seed entrypoint.sh into .pi-container/agent
fix: handle missing proxy IPv6 address gracefully
chore: bump pyyaml to 6.0.2
Versioning: Semantic Versioning¶
vMAJOR.MINOR.PATCH
^ ^ ^
| | └─ Bug fix (no new features, no breaking changes)
| └─────── New feature (backwards-compatible)
└────────────── Breaking change
- 0.x.y — Pre-release. Breaking changes are possible without a major bump, but we try to avoid them.
- 1.x.y — Stable.
MAJORbumps only for breaking changes.
The version is authoritative from the latest git tag (e.g., git tag -l | head -1).
pyproject.toml is informational only — the launch reads the version from git.
Schema version: config compatibility¶
Every pi-container release includes a template for the per-project configuration
at pi-coding-agent/default/. This template is seeded into each workspace's
.pi-container/ on first run.
The template config.yaml includes a schema_version field that matches the
pi-container version (e.g., "0.1.0"). At launch, the schema version in the
seeded config is checked against the app version (from the latest git tag). If
they don't match, the launch fails with a clear error message.
When to bump the schema version¶
Bump the schema_version in pi-coding-agent/default/config.yaml whenever you:
- Add a new required field to the template
- Change the type of an existing field
- Remove a field that users might still have in their configs
- Add a new file to
pi-coding-agent/default/(e.g., a new addon config)
The schema version is separate from the pi-container version — they should be kept in sync, but the schema version is what triggers the compatibility check.
User-facing behavior¶
When a user has an outdated config:
- The launch fails with: "Configuration incompatible with this version of pi-container"
- The error message lists the specific issues (missing fields, type mismatches, version mismatch)
- The error message suggests: "delete .pi-container and re-run to re-seed"
Users can also manually update schema_version in their local config.yaml to
match the new version, but this is not recommended — they should re-seed to get
the latest defaults and new fields.
Example: adding a new field¶
# pi-coding-agent/default/config.yaml
schema_version: "0.2.0" # Bumped from "0.1.0"
# ... existing fields ...
# New field in this release
custom:
enabled: false
Users with schema_version: "0.1.0" in their local config will see an error on
next launch. They must rm -rf .pi-container and re-run to get the new field.
Release skill¶
The release skill automates the version bump, changelog update, validation, and git tag steps described in the next section. It is designed for use by pi, the coding agent:
How it works¶
- Determine the version — asks the user for the version number, or suggests patch/minor/major based on the changes since the last tag.
- Run
release.sh— bumpspyproject.toml, bothschema_versionfields, regeneratesuv.lock, and runsvalidate_versions.py+ lint + tests. - Update
CHANGELOG.md— moves[Unreleased]entries into a new version block with today's date, enforcing reverse chronological order. - Amend the release commit — adds the changelog update to the existing commit created by the script (never re-run the script, or versions get double-bumped).
- Tag and push — creates
v<version>and pushes toorigin.
CI triggers on the tag push and creates the GitHub Release automatically.
When to use the skill vs. manual steps¶
| Scenario | Use |
|---|---|
| You're chatting with pi | Release skill |
| You need to do a release from a different machine | Manual steps in the next section |
| You need to inspect or customise each step | Manual steps |
The skill performs the same operations as the manual steps — it is a convenience wrapper.
Creating a release¶
The version is authoritative from the latest git tag. Three places must always stay in sync:
| Source | Location |
|---|---|
| Git tag | v<version> (e.g. v0.2.0) |
| Python package version | pyproject.toml → [project].version |
| Config schema version | pi-coding-agent/default/config.yaml → schema_version |
| Runtime config version | .pi-container/config.yaml → schema_version |
The validate_versions.py script runs in CI (not pre-commit) because git has
no pre-tag hook — the version cross-check can only pass once the tag exists,
which happens after the commit. The runtime config
(.pi-container/config.yaml) is checked at launch time by src/config_schema.py
— if it does not match the latest git tag, the container refuses to start.
Steps¶
- Make sure
mainis green — CI must pass on the commit you want to release (checkandtestjobs). - Update
CHANGELOG.md— Move the[Unreleased]entries into a new version block with the release date, following Keep a Changelog. - Bump the version in
pyproject.toml([project] version). - Regenerate
uv.lock—uv lock(the lockfile embeds the project version). - Bump
schema_versionin the seed template —pi-coding-agent/default/config.yaml. This is what new workspaces get on first run. - Bump
schema_versionin the runtime config —.pi-container/config.yaml. This is what the currently running container uses. Seeding is copy-once (missing-only), so updating the template alone will not update the runtime config. - Validate locally before pushing:
uv run python3 .github/workflows/scripts/validate_versions.py
pre-commit run --all-files --show-diff-on-failure
uv run pytest --cov
git add CHANGELOG.md pyproject.toml \
uv.lock \
pi-coding-agent/default/config.yaml \
.pi-container/config.yaml
git commit -m "chore: release v0.2.0"
git push origin main
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin v0.2.0
What CI does¶
The ci.yml workflow triggers on push to refs/tags/v*:
check— runs lint, tests, andvalidate_versions.py.test— runs the full test suite with coverage, updates the coverage badge onmain.release— if both jobs pass, creates a GitHub Release viasoftprops/action-gh-releasewith auto-generated release notes from merged commits since the last tag.
Example¶
# 1. Update CHANGELOG.md: move [Unreleased] → [0.2.0] - 2026-07-04
# 2. Bump pyproject.toml version to "0.2.0"
# 3. Bump schema_version in pi-coding-agent/default/config.yaml to "0.2.0"
# 4. Bump schema_version in .pi-container/config.yaml to "0.2.0"
# 5. Regenerate uv.lock: uv lock
# 6. Validate and commit:
git add CHANGELOG.md pyproject.toml \
uv.lock \
pi-coding-agent/default/config.yaml \
.pi-container/config.yaml
git commit -m "chore: release v0.2.0"
git tag -a v0.2.0 -m "Release v0.2.0"
git push origin main
git push origin v0.2.0
After the release¶
Users clone the repo, check out the tag, and run build.sh to build the Docker
images for their local runtime (Apple container, podman, or docker).
If a workspace's .pi-container/config.yaml is outdated (e.g. a user skipped
step 5 above), the launch fails with:
schema_version mismatch: config has '0.1.1', the latest pi-container version is '0.2.0'.
The user must update schema_version in .pi-container/config.yaml, or delete
.pi-container and re-run to re-seed from the new template.
Rolling back¶
To revert a release, revert the commit on main and create a new patch release
(e.g., v1.0.1). Do not delete tags — they are historical record.
Environment variables for the release job¶
The CI release job needs no extra secrets beyond what CI already provides.
GITHUB_TOKEN is used automatically by the GitHub Release action.