11 Commits

Author SHA1 Message Date
pi 1d1283f942 refactor: FROM opencode-devbox:latest-pi-only (lean, no opencode)
Re-point the re-brand at the new pi-only variant instead of with-pi, so
pi-devbox stays a lean pi-focused image (no opencode) while the pi install
logic still lives in one place upstream. This keeps pi-devbox meaningfully
distinct from opencode-devbox:latest-with-pi.

- Dockerfile: BASE_IMAGE default -> joakimp/opencode-devbox:latest-pi-only.
- smoke-test.sh: size threshold 2900 -> 2750 MB (pi-only = with-pi minus
  opencode's ~145 MB binary).
- Docs (README/AGENTS/DOCKER_HUB/CHANGELOG/docker-compose): drop the
  'also contains opencode' notes; describe pi-only basis and the distinction
  from with-pi.

Publish ordering unchanged: release opencode-devbox first so latest-pi-only
carries the target pi version, then tag here (smoke asserts pi --version).
2026-06-03 16:14:05 +02:00
pi c139be326f refactor: re-brand the opencode-devbox with-pi variant (single source of truth)
pi-devbox no longer installs pi itself. The Dockerfile is now a thin
FROM joakimp/opencode-devbox:latest-with-pi (overridable via BASE_IMAGE),
inheriting pi + pi-toolkit + pi-extensions + pi-fork (fork) +
pi-observational-memory (recall) + the LAN-access helper + all base tooling
from the single source of truth. Eliminates the install-logic duplication
that drifted against opencode-devbox/Dockerfile.variant (decision #3).

Consequences (documented in CHANGELOG/AGENTS):
- The image now ALSO contains opencode (with-pi has INSTALL_OPENCODE=true).
  A leaner pi-only image would need a dedicated pi-only variant upstream.
- Publish ordering: release opencode-devbox first so latest-with-pi carries
  the target pi version, THEN tag this repo. The smoke test asserts
  pi --version matches the tag (EXPECTED_PI_VERSION) and fails loudly if the
  base is stale — turning the version coupling into an enforced ordering guard.

CI: drop PI_VERSION build-arg (Dockerfile installs nothing); keep tag->version
resolution to feed the smoke base-freshness guard. Smoke adds fork/recall
clone + node_modules + settings.json registration checks; size threshold
2200 -> 2900 MB (now tracks with-pi). Docs updated across README, AGENTS,
DOCKER_HUB, .env.example, docker-compose.
2026-06-03 15:51:41 +02:00
pi 1587a84579 Cut v0.78.0 — pi 0.77.0→0.78.0
Publish Docker Image / smoke (push) Successful in 2m24s
Publish Docker Image / publish (push) Successful in 13m13s
Publish Docker Image / update-description (push) Successful in 7s
2026-05-31 22:26:21 +02:00
pi 32df96f0ea Cut v0.77.0 — pi 0.76.0→0.77.0
Publish Docker Image / smoke (push) Successful in 2m25s
Publish Docker Image / publish (push) Successful in 13m10s
Publish Docker Image / update-description (push) Successful in 10s
First container build on pi 0.77 line (published upstream 2026-05-28).
Built against unchanged joakimp/opencode-devbox:base-latest (same as
v0.76.0 — SSH-CM, gitleaks, git-crypt all carry forward).

Notable pi 0.77.0 upstream:
- Claude Opus 4.8 support
- --exclude-tools / -xt for selective tool disablement
- Headless Codex subscription login (device-code auth)
- Streaming-aware extension input (InputEvent.streamingBehavior)
- Long bugfix list (startup timing, signal handling, terminal
  protocol detection, Windows MSYS2 fixes, provider metadata
  cleanups, session disposal abort, etc).

Also folds the previously-Unreleased CI retry-wrapper change
(2d39766) into this release block. Second publish exercising the
cache-export-disabled workflow; first to exercise the 3-attempt
retry wrapper through the publish path.

See CHANGELOG v0.77.0 for full notes.
2026-05-29 09:07:47 +02:00
pi 2d397663d5 ci: workflow-level 3-attempt retry around buildx build --push
Belt-and-braces against transient registry-1.docker.io blips (rate
limits, brief 5xx, CDN flap). Replaces docker/build-push-action@v7 with
a shell: bash step that runs docker buildx build --push in a for-loop
with backoff (15s, 30s).

Does NOT mask deterministic failures: a true regression (e.g. the
cache-export 400 we hit 2026-05-23..28) fails all 3 attempts
identically and the job still fails by design. Orthogonal layer to
both cache-export disablement and the ci-release-watcher skill's
transient-rerun heuristic.

No image-side change.
2026-05-28 16:32:34 +02:00
joakimp e6a21f36f1 Cut v0.76.0 — pi 0.76.0 + inherit SSH-CM/gitleaks from base-latest
Publish Docker Image / smoke (push) Successful in 2m18s
Publish Docker Image / publish (push) Successful in 14m11s
Publish Docker Image / update-description (push) Successful in 6s
pi 0.75.5 → 0.76.0 (published upstream 2026-05-27 20:03 UTC). First
pi-devbox release built against opencode-devbox base-latest carrying the
SSH ControlMaster bake-in (commit 668592d) and gitleaks (73a7f96) — both
inherited transparently with no Dockerfile change here. PI_VERSION is
resolved from the git tag by the workflow (v0.75.5b cache-hit fix), so
no Dockerfile default bump needed.

Workflow change: registry cache-export removed from publish step. buildkit
mode=max cache-export to registry-1.docker.io reproducibly returns HTTP 400
(Hub-CDN protocol mismatch with buildx 0.34.x, surfaced ~2026-05-23).
Diagnosed during opencode-devbox v1.15.12 manual publish: image push works,
only --cache-to fails. Pi-devbox would hit the same regression on the next
tag push without this fix. See opencode-devbox CHANGELOG v1.15.12 for the
full root-cause analysis. Pi-devbox is single-stage with a tiny diff (npm
install pi only) on top of base-latest, so builds are fast even uncached.
2026-05-28 10:43:52 +00:00
joakimp 9b305c9f7e Doc: note SSH ControlMaster fix arrives via opencode-devbox base
Symmetric with the gitleaks/git-crypt inherit-note already present.
Cross-references opencode-devbox commit 668592d (Unreleased), which
bakes /etc/ssh/ssh_config.d/00-devbox-controlmaster.conf with a
writable /tmp/sshcm ControlPath. pi-devbox picks this up automatically
on its next build against base-latest; no Dockerfile change here.

Documents the symptom users see today inside pi-devbox <= v0.75.5b
(unix_listener Read-only file system on \~/.ssh/cm) and the fact
that pi --ssh user@host inside the container is currently silently
broken until the cascade lands.
2026-05-26 18:29:00 +00:00
joakimp 5d9208c547 Doc: note gitleaks + git-crypt arrive via opencode-devbox base
No Dockerfile install change here — pi-devbox FROMs joakimp/opencode-
devbox:base-latest which gained gitleaks (and explicit acknowledgment
of git-crypt) in opencode-devbox commit adding both to the base layer.
The next pi-devbox release built against a fresh base-latest digest
inherits both with zero work on this side.

CHANGES

Dockerfile — comment block at top updated to name git-crypt + gitleaks
in the 'inherited from base' toolset enumeration. Helps future
readers: one less reason to think 'I need to install gitleaks here'.

CHANGELOG.md — new Unreleased entry pointing at the opencode-devbox
base-side change for full detail. Will be promoted whenever the next
pi-devbox release ships (probably alongside the next pi npm bump past
0.75.5).

Holding off on tagging — pi upstream still at 0.75.5, baseline release
v0.75.5b is already current with that. Will ride along with next pi
bump.
2026-05-24 15:49:38 +00:00
joakimp 34cae2a1d2 Cut v0.75.5b — fix cache-hit silent same-bytes regression
Publish Docker Image / smoke (push) Successful in 2m18s
Publish Docker Image / publish (push) Successful in 12m59s
Publish Docker Image / update-description (push) Successful in 11s
ALL FOUR releases v0.74.0 -> v0.75.5 had been shipping the same image
bytes due to a Docker layer-cache hit on the bare 'npm install -g
@earendil-works/pi-coding-agent' command (when PI_VERSION=latest).
The command string is identical across builds, so the layer-hash is
identical, so registry buildcache (cache-from/cache-to) silently
reuses the layer from whatever pi version was current when the cache
was first populated.

Verification: docker manifest inspect joakimp/pi-devbox:vX.Y.Z showed
identical SHA256 digests on both linux/amd64 and linux/arm64 for
v0.74.0, v0.75.3, v0.75.4, v0.75.5. Users on :latest were getting
whatever pi version was baked into the v0.74.0 build.

DISCOVERED 2026-05-23 by user trying to update pi-devbox on MBP-M1
and seeing pi 0.74.0 reported despite pulling v0.75.5.

CHANGES

.gitea/workflows/docker-publish.yml — both smoke and publish jobs
get a new 'Resolve PI_VERSION from tag' step that strips the leading
'v' and any trailing letter suffix from github.ref_name. Result is
passed as a build-arg to docker/build-push-action so the npm install
layer's hash includes the concrete version, forcing cache miss when
pi bumps.

scripts/smoke-test.sh — new run_expect helper that asserts pi
--version contains the EXPECTED_PI_VERSION env var. Smoke job sets
this from the resolve step output. Would have caught this regression
on v0.75.3.

Dockerfile — comment block above ARG PI_VERSION=latest documenting
the cache-hit footgun. The 'if latest' branch in the install RUN is
preserved for local dev convenience but never fires in CI now.

AGENTS.md — new convention bullet explaining the cache-hit class of
bug and noting the latent same-bug in opencode-devbox's with-pi
variants (currently masked by OPENCODE_VERSION bumps; will manifest
when cutting a vN.N.Nb-style opencode-version-unchanged release that
only bumps pi).

CHANGELOG.md — full entry under v0.75.5b describing the recovery,
the silent-failure mechanism, and the verification steps.

NO IMAGE-CONTENT CHANGES vs v0.75.5 INTENT. This build produces the
actual pi 0.75.5 image content that v0.75.5 was supposed to ship.

NEXT FOLLOWUP (parked, not in this commit)

opencode-devbox should get the same workflow change for its
build-variant-with-pi and build-variant-omos-with-pi jobs. Currently
masked because every release also bumps OPENCODE_VERSION which
invalidates the cache, but that masking would fail on a pi-only bump
release.
2026-05-23 22:10:08 +02:00
joakimp dff3092338 AGENTS: note pi changelog source is npm-tarball CHANGELOG.md
Publish Docker Image / publish (push) Has been cancelled
Publish Docker Image / update-description (push) Has been cancelled
Publish Docker Image / smoke (push) Failing after 14m8s
Companion to opencode-devbox's 'Upstream sources' section. Pi's npm
package ships a rich CHANGELOG.md with New Features / Added / Changed
/ Fixed sections — but the npm registry metadata ('npm view') doesn't
include the changelog body. Surface the 'npm pack + tar' recipe in
the release-day checklist so future-pi (and human-pi) doesn't try to
derive notes from npm view alone.

Doc-only, no CI implications.
2026-05-23 19:26:48 +02:00
joakimp c7f7f97754 Cut v0.75.5 — pi 0.75.4 -> 0.75.5
Publish Docker Image / smoke (push) Successful in 2m40s
Publish Docker Image / publish (push) Failing after 14m9s
Publish Docker Image / update-description (push) Has been cancelled
One upstream patch release, two days after v0.75.4. PI_VERSION=latest
in Dockerfile resolves to 0.75.5 at build time, so no Dockerfile change
is needed; just a CHANGELOG promote.

Notable upstream changes (read tool card cleanup, faster Windows file
tools, more reliable pi update, custom adaptive-thinking knob, several
bash/Bedrock fixes) — see CHANGELOG.md for the full list.

Cache hit expected on opencode-devbox:base-latest (base-35ee5fe7861a).
Tagged together with opencode-devbox v1.15.10 — both releases go
through the queued CI runner overnight.
2026-05-23 19:14:54 +02:00
9 changed files with 357 additions and 96 deletions
+14
View File
@@ -9,6 +9,20 @@ WORKSPACE_PATH=~/projects
# Path to SSH keys on host
SSH_KEY_PATH=~/.ssh
# ── LAN access from the container (host-OS-agnostic) ─────────────────
# On VM-backed hosts (macOS OrbStack / Docker Desktop) the container can't
# reach the host's directly-attached LAN peers by default. The entrypoint
# then sets up the host as an SSH jump (use the `dssh` alias, or add
# `ProxyJump host` to targets in your bind-mounted ~/.ssh/config). On native
# Linux Docker the LAN is reachable directly and this is a no-op.
# See the opencode-devbox README for the full walkthrough.
#
# DEVBOX_LAN_ACCESS: auto (default) | jump | off
# DEVBOX_LAN_ACCESS=auto
# HOST_SSH_USER: your username on the host (required for the jump). On first
# start the entrypoint prints the public key to authorize on the host.
# HOST_SSH_USER=
# ── Git Configuration ────────────────────────────────────────────────
GIT_USER_NAME=
GIT_USER_EMAIL=
+64 -9
View File
@@ -33,6 +33,22 @@ jobs:
- uses: docker/setup-buildx-action@v4
with: {driver-opts: network=host}
# Derive PI_VERSION from the tag (e.g. v0.78.0 -> 0.78.0; v0.78.0b -> 0.78.0).
# Since the refactor to FROM opencode-devbox:latest-with-pi, this repo no
# longer installs pi itself — pi comes from the base image. We still resolve
# the tag version and feed it to the smoke test as EXPECTED_PI_VERSION: the
# smoke asserts the inherited base actually carries this pi version, which
# turns the version coupling into an enforced publish-ordering guard (it
# fails loudly if latest-with-pi is stale relative to this tag).
- name: Resolve PI_VERSION from tag
id: resolve
run: |
TAG="${{ github.ref_name }}"
PI_VERSION="${TAG#v}"
PI_VERSION=$(echo "$PI_VERSION" | sed 's/[a-z]*$//')
echo "pi_version=${PI_VERSION}" >> "$GITHUB_OUTPUT"
echo "Resolved PI_VERSION=${PI_VERSION} from tag ${TAG}"
- name: Build (amd64, load to local daemon)
uses: docker/build-push-action@v7
with:
@@ -43,6 +59,8 @@ jobs:
tags: pi-devbox:smoke
- name: Smoke test
env:
EXPECTED_PI_VERSION: ${{ steps.resolve.outputs.pi_version }}
run: bash scripts/smoke-test.sh pi-devbox:smoke
publish:
@@ -81,15 +99,52 @@ jobs:
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Build and push (amd64 + arm64)
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=registry,ref=${{ env.IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.IMAGE }}:buildcache,mode=max
# See the smoke job for why the tag version is resolved (now used only for
# the base-freshness smoke guard; pi is no longer installed in this repo).
- name: Resolve PI_VERSION from tag
id: resolve
run: |
TAG="${{ github.ref_name }}"
PI_VERSION="${TAG#v}"
PI_VERSION=$(echo "$PI_VERSION" | sed 's/[a-z]*$//')
echo "pi_version=${PI_VERSION}" >> "$GITHUB_OUTPUT"
echo "Resolved PI_VERSION=${PI_VERSION} from tag ${TAG}"
- name: Build and push (amd64 + arm64) — with retry
shell: bash
env:
TAGS: ${{ steps.tags.outputs.tags }}
run: |
set -euo pipefail
# Convert newline-delimited TAGS env var (build-push-action's native
# format from the `Compute tags` step) into a bash array of -t flags.
TAG_FLAGS=()
while IFS= read -r t; do [[ -n "$t" ]] && TAG_FLAGS+=( -t "$t" ); done <<< "${TAGS}"
# 3-attempt retry around `docker buildx build --push` for transient
# registry-1.docker.io blips (rate limits, CDN flap, brief 5xx).
# The build itself is now trivial (FROM opencode-devbox:latest-with-pi
# + an empty layer) so it is fast even without registry cache.
# Registry cache stays disabled (buildkit mode=max cache-export hits a
# reproducible HTTP 400 from Hub CDN since ~2026-05-23; image push is
# unaffected). See opencode-devbox CHANGELOG v1.15.12.
for attempt in 1 2 3; do
echo "==> Build+push attempt ${attempt}/3"
if docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
"${TAG_FLAGS[@]}" \
.; then
echo "==> Attempt ${attempt} succeeded"
exit 0
fi
if [[ "${attempt}" -lt 3 ]]; then
backoff=$(( attempt * 15 ))
echo "==> Attempt ${attempt} failed, sleeping ${backoff}s before retry"
sleep "${backoff}"
fi
done
echo "==> All 3 build+push attempts failed"
exit 1
update-description:
needs: publish
+30 -15
View File
@@ -1,10 +1,11 @@
# AGENTS.md — pi-devbox
Container image that adds pi coding-agent on top of the opencode-devbox base image.
Container image that re-brands the opencode-devbox **pi-only** variant as a
pi-focused image. As of 2026-06-03 it no longer installs pi itself.
## Repository layout
- `Dockerfile`single-stage build, `FROM opencode-devbox:base-latest`, installs pi + companion repos
- `Dockerfile`thin re-brand: `FROM opencode-devbox:latest-pi-only` (overridable via `BASE_IMAGE` arg). No install logic of its own — pi + companions are inherited from the pi-only variant (built `INSTALL_OPENCODE=false`, so **no opencode** — that's the distinction from `latest-with-pi`). This refactor removed the install-logic duplication that used to drift against `opencode-devbox/Dockerfile.variant`.
- `docker-compose.yml` — compose file for local use
- `.env.example` — environment variable template
- `scripts/smoke-test.sh` — sanity checks run by CI before pushing to Docker Hub
@@ -13,38 +14,52 @@ Container image that adds pi coding-agent on top of the opencode-devbox base ima
## Versioning scheme
- Tags follow the pi npm version: `v{pi_version}[letter]`
- Bump `PI_VERSION` build-arg default in `Dockerfile` when cutting a new release
- The image inherits pi from `latest-pi-only`, so the **publish ordering matters**: rebuild opencode-devbox first so `latest-pi-only` carries the target pi version, *then* tag this repo. The smoke test asserts `pi --version` matches the tag (`EXPECTED_PI_VERSION`) and fails loudly if the base is stale.
- Docker Hub: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest`
## Release-day checklist
1. Bump `PI_VERSION` in `Dockerfile` (or leave as `latest` to pick up current)
1. Ensure opencode-devbox has been released so `joakimp/opencode-devbox:latest-pi-only` carries the target pi version (and the fork/recall extensions). This is the hard prerequisite — the smoke guard enforces it.
2. Update `CHANGELOG.md`: promote `Unreleased``vX.Y.Z — YYYY-MM-DD`
3. Add fresh `## Unreleased` section
4. Commit, tag `vX.Y.Z`, push tag → CI fires automatically
When drafting CHANGELOG entries, pull pi's release notes from the
`CHANGELOG.md` shipped inside the npm tarball:
```bash
cd /tmp && npm pack @earendil-works/pi-coding-agent@<version>
tar -xzf earendil-works-pi-coding-agent-<version>.tgz package/CHANGELOG.md
head -40 package/CHANGELOG.md
```
Pi's CHANGELOG has rich New Features / Added / Changed / Fixed sections
per version. Don't try to derive notes from the npm registry metadata
(`npm view`) — it doesn't include the changelog body.
## Key facts
- **Base image**: `joakimp/opencode-devbox:base-latest` — rebuilt whenever opencode-devbox cuts a new base
- **pi binary**: baked at `/usr/bin/pi` (system npm prefix); `NPM_CONFIG_PREFIX=/home/developer/.pi/npm-global` at runtime so user-installed pi/packages land on the named volume
- **Companion repos**: pi-toolkit and pi-extensions cloned to `/opt/` at build time; `entrypoint-user.sh` (inherited from base) deploys symlinks to `~/.pi/agent/` on container start
- **MemPalace**: fully operational — inherited from base image; bridge extension deployed by entrypoint
- **Base image**: `joakimp/opencode-devbox:latest-pi-only` — the single source of truth for the pi install + companions. Rebuilt whenever opencode-devbox releases.
- **Inherited content**: pi (`/usr/bin/pi`), pi-toolkit, pi-extensions, pi-fork (`fork`), pi-observational-memory (`recall`), the mempalace bridge, the LAN-access helper, entrypoints, and all base dev tooling. The pi-only variant is built `INSTALL_OPENCODE=false`, so the image does **not** contain opencode.
- **Companion repos**: cloned to `/opt/` by the pi-only build; `entrypoint-user.sh` (inherited) deploys/registers them on container start.
- **MemPalace**: fully operational — inherited from base; bridge extension deployed by entrypoint.
## Conventions
- Do NOT call `mempalace-toolkit/install.sh` in the Dockerfile — the base entrypoint handles it
- `NPM_CONFIG_PREFIX=/usr` must be set per-RUN for any build-time `npm install -g` to keep baked binaries off the volume-shadowed path
- The smoke test threshold is 2200 MB — update if the image legitimately grows past it
- This repo no longer installs pi or clones companion repos — do **not** re-add that logic here. Change it in `opencode-devbox/Dockerfile.variant` (the single source of truth) instead.
- The smoke test threshold is 2750 MB (tracks the pi-only variant) — update if the image legitimately grows past it.
- The CI still resolves the tag's pi version, but only to feed `EXPECTED_PI_VERSION` to the smoke base-freshness guard — it is no longer passed as a build-arg (nothing in the Dockerfile consumes it).
- To pin a specific base build instead of tracking `latest-pi-only`, override the `BASE_IMAGE` arg (a version tag or a digest).
## Documentation drift sweep
Before committing any non-trivial change, check that prose still matches code. Drift hotspots in this repo:
- `README.md` — quick-start examples, env-var table, base-image reference (must match `FROM` in `Dockerfile`).
- `AGENTS.md` (this file) — `Key facts` block (pi binary path, `NPM_CONFIG_PREFIX`, base-image tag), smoke-test threshold number.
- `README.md` — quick-start examples, env-var table, base-image reference (must match `FROM` in `Dockerfile`), "what's inside" (fork/recall; no opencode).
- `AGENTS.md` (this file) — `Key facts` block (base-image tag, inherited content), smoke-test threshold number.
- `CHANGELOG.md` — promote `Unreleased` only on tag, but record post-release fixes in a fresh `Unreleased` block.
- `DOCKER_HUB.md` — hand-maintained slim Hub description; sync anything user-facing that changes (env vars, run command, base image).
- `.env.example` — hand-updated, must match Dockerfile/entrypoint env vars.
- `Dockerfile` `PI_VERSION` ARG default — if you intend to pin (rather than `latest`), bump it on release.
- `.env.example` — hand-updated, must match Dockerfile/entrypoint env vars (including the inherited LAN-access knobs).
- `Dockerfile` `BASE_IMAGE` ARG default — the pi-only tag this image tracks.
Quick triage: `git diff --name-only HEAD | xargs -I{} grep -l 'thing-you-changed' README.md AGENTS.md DOCKER_HUB.md CHANGELOG.md .env.example`.
+137
View File
@@ -8,6 +8,143 @@ Tags follow the pi npm version: `v{pi_version}[letter]` — bare tag for the fir
## Unreleased
### Changed: refactored to re-brand the opencode-devbox `pi-only` variant
pi-devbox no longer installs pi itself. The `Dockerfile` is now a thin
`FROM joakimp/opencode-devbox:latest-pi-only` (overridable via the `BASE_IMAGE`
arg), inheriting pi + pi-toolkit + pi-extensions and all base tooling from the
single source of truth. This eliminates the install-logic duplication that
used to drift against `opencode-devbox/Dockerfile.variant`.
The `pi-only` variant is built with `INSTALL_OPENCODE=false`, so this image
stays lean and pi-focused — it does **not** carry opencode, and remains
distinct from `opencode-devbox:latest-with-pi` (which has both).
### Added (inherited from the pi-only variant)
- **`fork` tool** (pi-fork) and **`recall` tool** (pi-observational-memory),
baked into `/opt` with `node_modules` and registered at runtime.
- **Host-OS-agnostic LAN access**: on VM-backed hosts (macOS OrbStack /
Docker Desktop) the entrypoint sets up the host as an SSH jump to reach LAN
peers (`dssh` alias; `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` env). No-op on
native Linux. See the opencode-devbox README for details.
### Consequences / notes
- **Publish ordering**: release opencode-devbox first so `latest-pi-only`
carries the target pi version, *then* tag this repo. The smoke test asserts
`pi --version` matches the tag and fails loudly if the base is stale.
- CI no longer passes `PI_VERSION` as a build-arg (the Dockerfile installs
nothing); it still resolves the tag version to feed the smoke base-freshness
guard. Smoke size threshold 2200 → 2750 MB (now tracks the pi-only variant).
_pi version unchanged at `0.78.0` (still latest)._
## v0.78.0 — 2026-05-29
pi `0.77.0``0.78.0` bump (first container build on the pi 0.78 line, published upstream 2026-05-29). Built against `joakimp/opencode-devbox:base-latest` (unchanged from the v0.77.0 build).
### Bumped: pi 0.77.0 → 0.78.0
**New Features**
- **Named startup sessions** — `--name` / `-n` sets the session display name before startup across interactive, print, JSON, and RPC modes.
- **Clickable file tool paths** — built-in file tool titles render OSC 8 `file://` hyperlinks when the terminal supports them, including supported tmux clients.
**Added**
- Exported `convertToPng` for extension authors.
- Exported `parseArgs` and type `Args` for extension authors.
- Added a resume command hint when exiting interactive sessions.
- Added custom Amazon Bedrock request header support.
**Fixed**
- Fixed early interactive input typed before the prompt loop starts so it is buffered instead of dropped.
- Fixed OpenRouter Moonshot Kimi K2.6 requests to use `system` instead of unsupported `developer` messages.
- Fixed OSC 8 hyperlinks to pass through tmux when the client supports them.
- Fixed ANSI text wrapping to avoid stack overflows on very long wrapped lines.
- Fixed OpenAI Codex Responses SSE streams to abort response body reads after terminal events.
## v0.77.0 — 2026-05-29
pi `0.76.0``0.77.0` bump (first container build on the pi 0.77 line, published upstream 2026-05-28). Built against `joakimp/opencode-devbox:base-latest` (unchanged from the v0.76.0 build — same SSH-CM, gitleaks, git-crypt baked in).
### Bumped: pi 0.76.0 → 0.77.0
Notable upstream changes (from pi's CHANGELOG):
- **Claude Opus 4.8 support** — Anthropic Opus 4.8 model metadata + adaptive-thinking coverage updated.
- **Selective tool disablement** — `--exclude-tools` / `-xt` disables specific built-in, extension, or custom tools while leaving the rest available.
- **Headless Codex subscription login** — `/login` can use device-code auth for ChatGPT Plus/Pro Codex subscriptions; browser login remains the default.
- **Streaming-aware extension input** — `InputEvent.streamingBehavior` lets extensions distinguish idle prompts from mid-stream steers and queued follow-ups.
- **Bugfixes** — startup timing output excludes `createAgentSessionRuntime` work; OpenRouter DeepSeek V4 `xhigh` reasoning preserves OpenRouter's native effort; SIGTERM/SIGHUP exits run extension `session_shutdown` cleanup; keyboard protocol negotiation ignores delayed terminal responses (no false Kitty detection); Windows MSYS2 ucrt64 startup crash fixed via napi-rs 3.x clipboard addon; API-key/header config resolution treats plain strings as literals with `$ENV_VAR` / `${ENV_VAR}` interpolation and `$!` escaping; session disposal aborts in-flight agent/compaction/branch-summary/retry/bash work; `pi.getAllTools()` exposes per-tool `promptGuidelines`; OpenAI Codex Responses replay after switching from Anthropic extended-thinking sessions; Anthropic-compatible replay supports `allowEmptySignature` for providers returning empty thinking signatures; OpenAI/OpenRouter GPT-5.5 Pro thinking levels limited to supported efforts; OpenCode Go Kimi K2.6 thinking-off requests; Xiaomi Token Plan model metadata cleaned of unsupported variants; follow-up messages queued by `agent_end` extension handlers drain before idle; system prompt tool-selection guidance avoids unavailable file-exploration tools; fenced `diff` highlighting restored.
Workflow continues to derive `PI_VERSION` from the git tag (`v0.77.0``0.77.0`) and pass it as a build-arg per the v0.75.5b cache-hit fix; smoke test asserts `pi --version` matches.
### Inheritance from base
No base change in `joakimp/opencode-devbox:base-latest` since v0.76.0 — the v1.15.12 opencode-devbox release also reused the unchanged base. SSH ControlMaster on a writable socket path, gitleaks, and git-crypt continue to ride along from the base.
### CI
This is the second pi-devbox release exercising the cache-export-disabled workflow (after v0.76.0's clean publish on run #340) and the first to also exercise the 3-attempt retry wrapper added in 2d39766 along the publish path.
## v0.76.0 — 2026-05-28
pi `0.75.5``0.76.0` bump (first minor-version release on pi 0.76 line, published upstream 2026-05-27 20:03 UTC). Built against a fresh `joakimp/opencode-devbox:base-latest` which now bakes in SSH ControlMaster on a writable socket path, plus gitleaks and git-crypt — see the inherited-from-base notes below for details on each.
### Bumped: pi 0.75.5 → 0.76.0
Notable upstream changes (from pi's CHANGELOG):
- **Explicit session IDs for automation** — `--session-id <id>` lets scripts create or resume an exact project-local session.
- **RPC bash output can stay out of model context** — RPC clients can pass `excludeFromContext` to `bash` for commands whose output should not be sent with the next prompt.
- **More predictable provider retries and timeouts** — Codex WebSocket/SSE waits are bounded; `retry.provider.maxRetries` controls provider retries instead of hidden SDK defaults; SDK retries default to 0; quota/billing 429s are no longer retried behind Pi's retry handling.
- **Better terminal editing across environments** — Apple Terminal Shift+Enter detection on macOS, Windows Terminal OSC 8 hyperlink support, JetBrains truecolor with disabled OSC 8, Unicode-aware word navigation and deletion.
- **Bugfixes** — `pi update` bypasses npm/pnpm/Bun minimum-release-age gates; user-authored ordered-list markers preserved in transcripts; image attachment token estimates aligned with tool-result images; Codex Responses cache-affinity header fixed (`session-id` not `session_id`); OpenRouter/Poolside context-overflow detection; managed npm extension updates avoid peer-dependency conflicts; RpcClient handles unexpected child exits cleanly.
Workflow continues to derive `PI_VERSION` from the git tag (`v0.76.0``0.76.0`) and pass it as a build-arg, per the v0.75.5b cache-hit fix; smoke test asserts `pi --version` matches.
### Workflow change: registry cache-export disabled
- **`.gitea/workflows/docker-publish.yml`** — `cache-from`/`cache-to` removed from the `publish` step. buildkit's `mode=max` cache-export to `registry-1.docker.io` reproducibly returns HTTP 400 on the resumable-upload PUT, surfacing ~2026-05-23. Diagnosed during opencode-devbox v1.15.12's manual host-side publish: image push works fine, only `--cache-to` fails. See opencode-devbox CHANGELOG v1.15.12 `Unreleased` for the full root-cause analysis. The pi-devbox Dockerfile is single-stage with a tiny diff (npm install pi only) on top of `base-latest`, so builds are fast even without cache (~30-60s expected).
### Inherited from opencode-devbox base: SSH ControlMaster on a writable socket path
No Dockerfile change here — just a note that this release picks up the system-wide SSH ControlMaster default (`/etc/ssh/ssh_config.d/00-devbox-controlmaster.conf``ControlPath /tmp/sshcm/%r@%h:%p`, `ControlMaster auto`, `ControlPersist 10m`). This unblocks `ssh` and `pi --ssh user@host` from inside the container when `~/.ssh` is bind-mounted read-only from the host (the standard pi-devbox compose layout) — previously, OpenSSH's default `ControlPath` under `~/.ssh/cm/` was unwritable, so multiplexing failed with `unix_listener: cannot bind ... Read-only file system` and ssh fell back to fresh TCP connections, which on residential CGNAT manifested as banner-exchange timeouts. The fix is purely additive (per-container `/tmp/sshcm` dir, mode 700, created by entrypoint) and user `~/.ssh/config` per-host overrides still win because Debian's stock `ssh_config` sources `ssh_config.d/*.conf` before its own `Host *` block. See opencode-devbox CHANGELOG `v1.15.12` for the base-side details.
### Inherited from opencode-devbox base: gitleaks + git-crypt
No Dockerfile change here — just a note that this release includes `gitleaks` (newly added to the base) and `git-crypt` (was always installed via apt; just wasn't called out). Both are useful inside the container for repos that use a gitleaks pre-commit hook or git-crypt-encrypted canonical config and don't want host-side dependencies. See opencode-devbox CHANGELOG `v1.15.12` for the base-side details.
## v0.75.5b — 2026-05-23
Recovery release fixing a **silent cache-hit regression** discovered in the v0.75.5 image. All four releases v0.74.0 through v0.75.5 had been shipping the same image bytes because the Dockerfile's `npm install -g @earendil-works/pi-coding-agent` (bare, when `PI_VERSION=latest`) produces an identical layer-hash across builds. Combined with the registry buildcache, Docker reused the layer from whatever pi version was current when the cache was first populated.
Verification: `docker manifest inspect joakimp/pi-devbox:vX.Y.Z` showed identical SHA256 digests on both `linux/amd64` and `linux/arm64` for v0.74.0, v0.75.3, v0.75.4, v0.75.5. Users on `:latest` were getting whatever pi version was baked into the v0.74.0 build (probably 0.74.0 itself).
- **Workflow fix:** Both `smoke` and `publish` jobs now derive `PI_VERSION` from `github.ref_name` (e.g. `v0.75.5b``0.75.5`) and pass it as a build-arg. The Dockerfile's existing `if PI_VERSION=latest` branch never fires in CI now — always takes the `@${PI_VERSION}` branch — so the layer-hash includes the version and cache invalidates correctly.
- **Smoke test:** New `run_expect` helper asserts `pi --version` output contains `EXPECTED_PI_VERSION` (passed from the resolve step). Would have caught this regression on v0.75.3 if it had existed.
- **Dockerfile:** Comment added above `ARG PI_VERSION=latest` documenting the cache-hit footgun and pointing at the workflow's resolve step + AGENTS.md gotcha.
- **AGENTS.md:** New convention bullet explaining the cache-hit class of bug and noting the latent same-bug in opencode-devbox's `with-pi` variants (currently masked by OPENCODE_VERSION bumps).
No image-side changes vs v0.75.5 *intent* — this build will produce the actual pi 0.75.5 image content that v0.75.5 was supposed to ship.
## v0.75.5 — 2026-05-23
pi `0.75.4``0.75.5` bump (one upstream patch release, two days after v0.75.4).
Notable upstream changes (from pi's CHANGELOG):
- Cleaner read tool output (collapsed cards show only the read line; Ctrl+O expands).
- Faster file tools on Windows (async fs ops during streaming, image resize off the main TUI thread).
- More reliable package updates (`pi update` reconciles git-pinned refs without losing settings).
- Custom Anthropic-compatible adaptive thinking via `compat.forceAdaptiveThinking`.
- Several bash/read tool card display fixes; macOS Bun clipboard sidecar resolution; per-session OpenCode-Zen routing headers; Amazon Bedrock token cap fix.
Plus a new pi 0.74.2 rescue release advising Node 20 users to upgrade Node before going to newer Pi versions — the devbox base image runs newer Node so this doesn't affect us, but worth noting for users running pi outside the devbox.
- **Bump:** pi `@earendil-works/pi-coding-agent@0.75.5` baked at `/usr/bin/pi` (via `PI_VERSION=latest` resolving to 0.75.5 at build time — no Dockerfile change needed).
- No image-side changes from v0.75.4 beyond the pi npm version. Built on `joakimp/opencode-devbox:base-latest` which itself is unchanged (cache-hit on `base-35ee5fe7861a` since v1.14.50b).
## v0.75.4 — 2026-05-21
pi `0.75.3``0.75.4` bump (one upstream patch release). Plus the AGENTS.md documentation-drift sweep clause that landed on `main` between v0.75.3 and now.
+16 -7
View File
@@ -38,7 +38,14 @@ Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini
## What's inside
Inherited from [opencode-devbox base](https://hub.docker.com/r/joakimp/opencode-devbox):
pi-devbox is a re-brand of the **opencode-devbox `pi-only` variant** — it builds
`FROM joakimp/opencode-devbox:latest-pi-only` and adds no layers of its own.
The pi-only variant is built `INSTALL_OPENCODE=false`, so this image is lean
and pi-focused (no opencode — use `opencode-devbox:latest-with-pi` if you want
both).
Everything below is inherited from that single source of truth.
Base tooling:
- **Debian trixie** (latest stable)
- **Node.js** (LTS), **uv** (Python tooling), **rustup** (Rust on-demand)
@@ -47,19 +54,21 @@ Inherited from [opencode-devbox base](https://hub.docker.com/r/joakimp/opencode-
- **Gitea MCP** server
- **Dev tools**: neovim (LazyVim defaults), tmux, bat, eza, fzf, zoxide, ripgrep, git-lfs, make
- **Shell**: bash with history tuning, prefix-search bindings, fzf/zoxide integration
- **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the host is set up as an SSH jump to reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS`/`HOST_SSH_USER`). No-op on native Linux.
Added by pi-devbox:
pi and companions:
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned at build time via the `PI_VERSION` build-arg
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version set by the pi-only base build
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle` (manage extensions interactively), `mcp-loader` (load MCP servers via settings.json), `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
- **mempalace bridge** — MCP extension auto-symlinked from `/opt/mempalace-toolkit` so pi can read/write the same palace as opencode
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
- **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools
- **mempalace bridge** — MCP extension auto-symlinked so pi can read/write the same palace as opencode-devbox
The entrypoint deploys all of these on first container start. Re-running is idempotent and preserves user edits.
The entrypoint deploys/registers all of these on first container start. Re-running is idempotent and preserves user edits.
## Versioning
Tags follow the pi npm version: `v0.74.0`, `v0.75.0`, etc. `latest` always points at the most recent release. When pi cuts a new upstream version, this image is rebuilt and re-tagged to match.
Tags follow the pi npm version: `v0.74.0`, `v0.75.0`, etc. `latest` always points at the most recent release. The pi binary is inherited from `opencode-devbox:latest-pi-only`, so each release follows an opencode-devbox release that bakes the target pi version.
For container-level rebuilds on the same pi version (security updates, base bumps, fixes) the tag gets a letter suffix: `v0.74.0b`, `v0.74.0c`, …
+24 -48
View File
@@ -1,54 +1,30 @@
# pi-devbox — pi coding-agent container
#
# Builds on top of the opencode-devbox base image, which provides:
# Debian trixie, Node.js, AWS CLI, mempalace + MCP server, gitea-mcp,
# dev tools (neovim, tmux, bat, eza, fzf, zoxide, ripgrep, uv, rustup),
# user setup (developer/gosu), entrypoints, chromadb prewarm.
# As of 2026-06-03 this image is a thin re-brand of the opencode-devbox
# "pi-only" variant, which is the SINGLE SOURCE OF TRUTH for the pi install
# and its companion repos (pi-toolkit, pi-extensions, pi-fork,
# pi-observational-memory). Previously pi-devbox/Dockerfile duplicated that
# install logic, which drifted from opencode-devbox/Dockerfile.variant; this
# refactor eliminates the duplication.
#
# This image adds only pi itself and its companion repos.
# The "pi-only" variant is built with INSTALL_OPENCODE=false, so this image
# does NOT contain opencode — it stays a lean, pi-focused image, distinct
# from opencode-devbox:latest-with-pi (which carries both).
#
# Build args:
# BASE_IMAGE — base image to build from (default: base-latest)
# PI_VERSION — pi npm version: "latest" or a pinned version e.g. "0.74.0"
# PI_TOOLKIT_REF — git ref for pi-toolkit (default: main)
# PI_EXTENSIONS_REF — git ref for pi-extensions (default: main)
ARG BASE_IMAGE=joakimp/opencode-devbox:base-latest
# Everything is inherited from the pi-only image:
# pi + pi-toolkit + pi-extensions + pi-fork (fork) + pi-observational-memory
# (recall), the mempalace bridge, the LAN-access helper, entrypoints, and
# all base dev tooling.
#
# NOTE on PUBLISH ORDERING: rebuild opencode-devbox (so `latest-pi-only`
# carries the target pi version) BEFORE tagging this repo. The smoke test
# asserts `pi --version` matches this repo's tag and fails loudly if the
# base is stale — turning the version coupling into an enforced ordering check.
#
# Override BASE_IMAGE to pin a specific pi-only build (e.g. a version tag or a
# digest) instead of tracking latest-pi-only.
ARG BASE_IMAGE=joakimp/opencode-devbox:latest-pi-only
FROM ${BASE_IMAGE}
ARG PI_VERSION=latest
ARG PI_TOOLKIT_REF=main
ARG PI_EXTENSIONS_REF=main
# Install pi and clone companion repos.
# NPM_CONFIG_PREFIX is overridden to /usr so the baked binary lands at the
# system prefix — same pattern as opencode-devbox's variant Dockerfile.
# At runtime, NPM_CONFIG_PREFIX is reset to /home/developer/.pi/npm-global
# (inherited from base ENV) so user-installed packages land on the named
# volume and survive container recreate.
#
# git clone is wrapped in a retry loop because gitea.jordbo.se occasionally
# returns transient HTTP 500s on the first request after idle.
RUN set -e && \
git_clone_retry() { \
url="$1"; ref="$2"; dest="$3"; \
for i in 1 2 3 4 5; do \
if git clone --depth 1 --branch "$ref" "$url" "$dest"; then return 0; fi; \
rm -rf "$dest"; \
echo "git clone $url failed (attempt $i/5), retrying in $((i*5))s..."; \
sleep $((i*5)); \
done; \
return 1; \
} && \
if [ "${PI_VERSION}" = "latest" ]; then \
NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent ; \
else \
NPM_CONFIG_PREFIX=/usr npm install -g @earendil-works/pi-coding-agent@${PI_VERSION} ; \
fi && \
pi --version && \
git_clone_retry https://gitea.jordbo.se/joakimp/pi-toolkit.git "${PI_TOOLKIT_REF}" /opt/pi-toolkit && \
git_clone_retry https://gitea.jordbo.se/joakimp/pi-extensions.git "${PI_EXTENSIONS_REF}" /opt/pi-extensions && \
echo "pi-toolkit at $(cd /opt/pi-toolkit && git rev-parse --short HEAD)" && \
echo "pi-extensions at $(cd /opt/pi-extensions && git rev-parse --short HEAD)"
# WORKDIR / ENTRYPOINT / CMD inherited from base.
# WORKDIR / ENTRYPOINT / CMD and all tooling inherited from the base.
# No additional layers — the value here is the single-source-of-truth refactor.
+26 -14
View File
@@ -9,7 +9,12 @@ A Docker container with [pi coding-agent](https://github.com/earendil-works/pi)
## What's inside
Inherited from `opencode-devbox:base-latest`:
pi-devbox is a re-brand of the **opencode-devbox `pi-only` variant** — it `FROM`s
`opencode-devbox:latest-pi-only` and adds no layers of its own. Everything below
is inherited from that single source of truth. The pi-only variant is built with
`INSTALL_OPENCODE=false`, so this image is lean and pi-focused — no opencode.
Base tooling:
- **Debian trixie** (current stable)
- **Node.js** (LTS), **uv** (Python), **rustup** (Rust on-demand)
@@ -18,15 +23,19 @@ Inherited from `opencode-devbox:base-latest`:
- **Gitea MCP** server
- **Dev tools**: neovim (LazyVim), tmux, bat, eza, fzf, zoxide, ripgrep, jq, git-lfs, make
- **Shell**: bash with history tuning, prefix-search, fzf/zoxide integration
- **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the entrypoint sets up the host as an SSH jump so you can reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS`/`HOST_SSH_USER` env). No-op on native Linux.
Added by pi-devbox:
pi and companions:
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned at build time
- **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned by the pi-only base build
- **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — mosh/tmux-friendly keybindings (Shift+Enter, Ctrl+J, Alt+J newline), AWS env loader, settings template
- **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive`
- **mempalace bridge** — auto-symlinked MCP extension so pi reads/writes the same palace as opencode
- **`fork` tool** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall` tool** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) — baked into `/opt` and registered at runtime
- **mempalace bridge** — auto-symlinked MCP extension so pi reads/writes the same palace as opencode-devbox's palace
The entrypoint deploys all of these on first container start. Idempotent and preserves user edits.
(opencode itself is **not** included — that's the difference from `opencode-devbox:latest-with-pi`. If you want both opencode and pi in one image, use that variant instead.)
The entrypoint deploys/registers all of these on first container start. Idempotent and preserves user edits.
---
@@ -187,6 +196,8 @@ All config flows through `.env`. The full list (with annotations) is in [`.env.e
| `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | (unset) | AWS Bedrock static creds |
| `GITEA_ACCESS_TOKEN` / `GITEA_HOST` | (unset) | Gitea MCP server (optional) |
| `GITHUB_PERSONAL_ACCESS_TOKEN` | (unset) | GitHub MCP server / git ops over HTTPS |
| `DEVBOX_LAN_ACCESS` | `auto` | LAN-access mode: `auto` (jump only on VM-backed hosts), `jump`, `off` |
| `HOST_SSH_USER` | (unset) | Host username for the LAN SSH jump (see opencode-devbox README) |
| `LANG` / `LANGUAGE` / `LC_ALL` | `en_US.UTF-8` | Locale override |
---
@@ -197,34 +208,35 @@ Tags follow the pi npm package version: `v0.74.0`, `v0.75.0`, … `latest` alway
Container-level rebuilds on the same pi version (security updates, base bumps, fixes) get a letter suffix: `v0.74.0b`, `v0.74.0c`, …
When the upstream [pi npm package](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) cuts a new version, this image is rebuilt and re-tagged to match.
The pi binary is inherited from `opencode-devbox:latest-pi-only`, so a release of this image must be preceded by an opencode-devbox release that bakes the target pi version into `latest-pi-only`. The smoke test enforces this (it asserts `pi --version` matches the tag).
---
## Building from source
If you want to pin a specific pi version, change the base image, or hack on the Dockerfile:
This image is a thin re-brand of the pi-only variant, so building it just pulls
the base. To pin a specific pi-only build or hack on it:
```bash
git clone https://gitea.jordbo.se/joakimp/pi-devbox
cd pi-devbox
# Edit Dockerfile or override via build args:
# Default tracks latest-pi-only; override BASE_IMAGE to pin a build:
docker compose build \
--build-arg PI_VERSION=0.73.0 \
--build-arg BASE_IMAGE=joakimp/opencode-devbox:base-latest
--build-arg BASE_IMAGE=joakimp/opencode-devbox:v1.15.13-pi-only
docker compose up -d
```
To change the pi version, the pi extensions, or the install logic, edit
`opencode-devbox/Dockerfile.variant` (the single source of truth) and release
opencode-devbox — not this repo.
Build args supported:
| Arg | Default | Effect |
|---|---|---|
| `BASE_IMAGE` | `joakimp/opencode-devbox:base-latest` | Parent image — set to `joakimp/opencode-devbox:base-<sha>` for reproducible builds |
| `PI_VERSION` | `latest` | npm version of `@earendil-works/pi-coding-agent` |
| `PI_TOOLKIT_REF` | `main` | Git ref for [pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit) |
| `PI_EXTENSIONS_REF` | `main` | Git ref for [pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions) |
| `BASE_IMAGE` | `joakimp/opencode-devbox:latest-pi-only` | Parent image — set to a `:vX.Y.Z-pi-only` tag or a digest for reproducible builds |
---
+2 -1
View File
@@ -17,7 +17,8 @@ services:
# build:
# context: .
# args:
# PI_VERSION: "latest"
# # Pin a specific pi-only build instead of tracking latest-pi-only:
# BASE_IMAGE: "joakimp/opencode-devbox:v1.15.13-pi-only"
container_name: pi-devbox
stdin_open: true
tty: true
+44 -2
View File
@@ -17,7 +17,10 @@ set -euo pipefail
IMAGE="${1:?usage: $0 <image>}"
PASS=0; FAIL=0
SIZE_THRESHOLD_MB=2200
# Since the refactor to FROM opencode-devbox:latest-pi-only, this image equals
# the pi-only variant (pi + companions + fork/recall node_modules, NO opencode),
# so the threshold tracks pi-only's (2750 MB), not the old standalone 2200 MB.
SIZE_THRESHOLD_MB=2750
run() {
local label="$1"; local cmd="$2"
@@ -28,12 +31,33 @@ run() {
fi
}
# Stricter version of `run` that also asserts an expected substring in stdout.
# Used for catching the "image bytes silently identical to previous release"
# class of regression (Docker layer cache hit on `npm install -g <pkg>` because
# the bare command string is identical across builds, even when `latest` would
# resolve differently). Discovered 2026-05-23 — every pi-devbox release v0.74.0
# through v0.75.5 had been shipping the same image bytes.
run_expect() {
local label="$1"; local cmd="$2"; local expect="$3"
local out
out=$(docker run --rm --entrypoint="" "$IMAGE" sh -c "$cmd" 2>&1) || true
if echo "$out" | grep -Fq "$expect"; then
printf " ✅ %s (got %s)\n" "$label" "$expect"; PASS=$((PASS+1))
else
printf " ❌ %s — expected substring %q, got: %s\n" "$label" "$expect" "$out"; FAIL=$((FAIL+1))
fi
}
echo "=== pi-devbox smoke test: $IMAGE ==="
echo ""
# ── Basic binary checks ───────────────────────────────────────────────
echo "── Binaries ──"
run "pi" "pi --version"
if [ -n "${EXPECTED_PI_VERSION:-}" ]; then
run_expect "pi version matches build arg" "pi --version" "$EXPECTED_PI_VERSION"
else
run "pi" "pi --version"
fi
run "node" "node --version"
run "git" "git --version"
run "aws" "aws --version"
@@ -46,6 +70,12 @@ echo ""
echo "── Repo clones ──"
run "pi-toolkit clone" "test -d /opt/pi-toolkit && git -C /opt/pi-toolkit rev-parse --short HEAD"
run "pi-extensions clone" "test -d /opt/pi-extensions && git -C /opt/pi-extensions rev-parse --short HEAD"
# pi-fork (fork tool) + pi-observational-memory (recall tool) — inherited from
# the pi-only base, cloned to /opt with node_modules baked at build time.
run "pi-fork clone + node_modules" \
"test -f /opt/pi-fork/package.json && test -d /opt/pi-fork/node_modules"
run "pi-observational-memory clone + node_modules" \
"test -f /opt/pi-observational-memory/package.json && test -d /opt/pi-observational-memory/node_modules"
# ── Runtime deployment (needs entrypoint to run) ──────────────────────
echo ""
@@ -80,6 +110,18 @@ exec_test "extensions ≥ 4 (pi-extensions)" 'count=$(ls -1 $HOME/.pi/age
exec_test "mempalace.ts bridge" 'test -L $HOME/.pi/agent/extensions/mempalace.ts && echo ok'
exec_test "settings.json bootstrapped" 'test -f $HOME/.pi/agent/settings.json && echo ok'
# pi-fork + pi-observational-memory are registered by entrypoint-user.sh via
# `pi install /opt/<pkg>`, which runs slightly after the keybindings marker.
for i in $(seq 1 15); do
if docker exec "$CID" grep -q pi-observational-memory \
/home/developer/.pi/agent/settings.json 2>/dev/null; then
break
fi
sleep 1
done
exec_test "pi-fork registered (fork tool)" 'grep -q pi-fork $HOME/.pi/agent/settings.json && echo ok'
exec_test "pi-observational-memory registered (recall tool)" 'grep -q pi-observational-memory $HOME/.pi/agent/settings.json && echo ok'
# ── Image size ────────────────────────────────────────────────────────
echo ""
echo "── Image size ──"