v1.7.0: bundle pi-atelier at a pinned tag; pin pi to an audited 0.84.1
Publish Docker Image / resolve-versions (push) Successful in 9s
Lint / actionlint (push) Successful in 15s
Lint / hadolint (push) Successful in 13s
Publish Docker Image / base-decide (push) Successful in 8s
Publish Docker Image / build-base (push) Successful in 41m22s
Publish Docker Image / smoke-studio (push) Successful in 5m16s
Publish Docker Image / smoke (push) Successful in 7m31s
Publish Docker Image / build-variant-studio (push) Successful in 18m31s
Publish Docker Image / build-variant (push) Successful in 27m18s
Publish Docker Image / promote-base-latest (push) Successful in 11s
Publish Docker Image / update-description (push) Successful in 12s

Two changes that belong together, because the first is what makes the second
dangerous to get wrong.

pi-atelier (TUI sidebar + status rail) is now vendored to /opt/pi-atelier at
PI_ATELIER_REF=v0.8.0 and registered by entrypoint-user.sh — the pi-fork /
pi-observational-memory / pi-studio pattern, deliberately NOT
`pi install npm:pi-atelier`, which writes into ~/.pi/npm-global on the config
volume where it shadows the image and pins nothing. Unlike its siblings it gets
no `npm install`: atelier declares zero runtime deps (peerDeps only, satisfied by
the baked pi) and has no build step, so pi loads its TypeScript straight from the
checkout via package.json `pi.extensions`.

pi is no longer resolved to npm `latest` at build time. The pin lives in
Dockerfile.variant and CI reads it from there, so a local `docker build` and a CI
release ship the same versions by construction. The pin is a CHECKPOINT, NOT A
FREEZE: bumping stays a one-line change; what stops is *unreviewed* adoption of
whatever shipped that morning, in the same build that then gets tagged and
published. CI fails when a pin is not concrete or not actually published on npm,
and warns — never adopts — when npm latest moves ahead, naming what to re-check.

Why this pairing needed care: pi-atelier 0.6.0/0.7.0 wrap pi's PRIVATE TUI
renderer, and under pi 0.84 that wrapper recurses — pi hangs at startup burning
CPU with no error. Upstream fixed the recursion in 0.7.1 and restored the
non-overlapping split in 0.7.2; 0.8.0 is additive on top. atelier's own
peerDependencies still say >=0.80.7, which does not express that floor, so
nothing in npm metadata could have warned us. The floor is therefore encoded as
an executable rule — pi >= 0.84 => pi-atelier >= 0.7.1 — asserted in both
smoke-test.sh (build time) and recreate-sanity-check.sh (after a real recreate),
verified against a 4x4 version matrix.

Existing volumes needed migration, not just vendoring: a hand-installed
`npm:pi-atelier` entry is counted as already-registered by the entrypoint guard,
so every existing volume would have kept its unpinned npm copy — and a 0.6.x copy
next to pi 0.84 is exactly the startup hang. The entrypoint now drops that one
exact string (settings.json.bak.atelier.<ts> backup, distinct prefix so it cannot
clobber the template merge's backup in the same second) and lets the pinned /opt
copy register. Tested against a real settings.json: only that entry removed,
other packages and all keys intact, idempotent, and unparseable JSON leaves the
file untouched. DEVBOX_ATELIER=0 opts out entirely — in the entrypoint rather
than via `pi uninstall`, because this component's failure mode is "pi will not
start", which cannot be repaired from inside pi.

0.84.1 was audited for this release, not merely adopted: theme/TUI changes are
additive, the session format is unchanged (CURRENT_SESSION_VERSION = 3 in both
0.83.0 and 0.84.1 with an identical migrateV1ToV2/migrateV2ToV3 ladder, so
existing transcripts are neither migrated nor at risk and pi-session-repair stays
valid), and the Node engine floor is unmoved at >=22.19.0. CI resolves the
atelier tag to its PEELED commit SHA — atelier uses annotated tags, so the
unpeeled ref is a tag object, not a commit; pi-studio's lightweight tags never
exposed that distinction.

Also: docs for overriding the read-only ~/.ssh/config from the container —
container-only keys in ~/.ssh-local, hardened authorized_keys, the fact that
`from=` must allow the HOST's addresses because container egress is NAT'd through
it, and the macOS-only-keyword trap (`UseKeychain` is fatal to Linux OpenSSH and
takes out dssh/pi --ssh while the host keeps working). Corrects two claims in
"Naming LAN peers": ssh-lan.conf is not ProxyJump-only, and first-time creation
does need one restart because the Include is emitted only when the file already
exists at start.
This commit is contained in:
2026-08-07 21:34:41 +02:00
parent 62a2a79b1c
commit 43cd6e22f2
9 changed files with 667 additions and 39 deletions
+50
View File
@@ -218,6 +218,15 @@ _pkg_registered() {
fi
}
# True when a literal `npm:pi-atelier` entry is still present — the
# volume-resident registration the entrypoint migrates away from.
_npm_atelier_present() {
_s="$HOME/.pi/agent/settings.json"
[ -f "$_s" ] || return 1
command -v jq >/dev/null 2>&1 || return 1
jq -e '(.packages // []) | any(. == "npm:pi-atelier")' "$_s" >/dev/null 2>&1
}
if [ -f "$HOME/.pi/agent/settings.json" ]; then
for pkg in pi-fork pi-observational-memory; do
if _pkg_registered "$pkg"; then
@@ -234,6 +243,47 @@ if [ -f "$HOME/.pi/agent/settings.json" ]; then
fail "pi-studio NOT in settings.json packages[] (studio variant)"
fi
fi
# pi-atelier — vendored from v1.7.0 on. Absent on older images, and
# deliberately unregistered when DEVBOX_ATELIER=0; neither is a failure.
if [ -d /opt/pi-atelier ]; then
if [ "${DEVBOX_ATELIER:-1}" = "0" ]; then
if _pkg_registered pi-atelier; then
fail "pi-atelier still in packages[] despite DEVBOX_ATELIER=0"
else
pass "pi-atelier unregistered (DEVBOX_ATELIER=0, as requested)"
fi
elif _pkg_registered pi-atelier; then
pass "pi-atelier registered in settings.json packages[]"
else
fail "pi-atelier NOT in settings.json packages[] (sidebar will not load)"
fi
if _npm_atelier_present; then
fail "stale npm:pi-atelier still in packages[] — it resolves through the ~/.pi/npm-global VOLUME and shadows the pinned /opt copy (entrypoint migration did not run)"
fi
fi
fi
# ── pi <-> pi-atelier compatibility floor ─────────────────────────────
# atelier < 0.7.1 wraps pi's private TUI renderer in a way that recurses under
# pi >= 0.84: pi hangs at startup burning CPU, with no error message. atelier's
# own peerDependencies (>=0.80.7) do not encode this. Assert it here too, not
# just in the build-time smoke test: this script runs after a real
# `--force-recreate` on a live box, where a volume-resident old copy is exactly
# what could bite.
if [ -d /opt/pi-atelier ] && command -v jq >/dev/null 2>&1; then
_ge() { [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ]; }
_av=$(jq -r '.version // empty' /opt/pi-atelier/package.json 2>/dev/null || true)
_pv=$(pi --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || true)
if [ -n "$_av" ] && [ -n "$_pv" ]; then
if _ge "$_pv" 0.84.0 && ! _ge "$_av" 0.7.1; then
fail "pi $_pv with pi-atelier $_av — atelier < 0.7.1 hangs pi >= 0.84 at startup (bump PI_ATELIER_REF in Dockerfile.variant)"
else
pass "pi $_pv + pi-atelier $_av (compatibility floor OK)"
fi
else
warn "could not compare pi/pi-atelier versions (pi='$_pv' atelier='$_av')"
fi
fi
echo