Roll the unreleased changes into v1.6.0 (minor — significant base addition:
agent-browser + headless Chromium baked into every variant for browser
automation/front-end verification). Also includes the pi-devbox-version command
and the bundled pi-toolkit sonnet-5 template bump. No pi bump — CI resolves
latest pi from npm at build time (verified: 0.80.6→0.80.10 has no breaking
changes affecting pi-devbox).
Size concern ahead of the v1.6.0 base rebuild. agent-browser drives the full
chrome (verified headless: open/title/eval with the headless_shell removed), so
Playwright's chromium_headless_shell-* build is dead weight — drop it (~334MB),
and clean apt/npm caches in-layer. Net browser footprint ~625MB/arch.
CI disk is otherwise fine: build-base's 'Reclaim runner disk' step frees
~20-30GB (hostedtoolcache/dotnet/android/jvm) + docker prune before buildx.
hadolint clean.
Discoverability follow-up to baking agent-browser into the base: agents won't
reach for a browser they don't know they have (the exact gap that made this
capability easy to miss). Add a short pointer section to the pi-devbox managed
block — capability, the preset AGENT_BROWSER_EXECUTABLE_PATH, and
`agent-browser skills get core --full` for the command set. Pointer only; depth
stays in the skill. rootfs change → folds into the same base-<hash> rebuild.
CHANGELOG note updated.
Gives the agent a real browser it can drive so front-end work involving live
DOM/WebGL can be VERIFIED, not guessed. The agent-browser skill (from the
skillset repo) was a no-op without the binary; it now works out of the box.
- agent-browser CLI (standalone Rust, ships no browser) via npm, prefixed
NPM_CONFIG_PREFIX=/usr so it survives the ~/.pi/npm-global volume.
- Chromium fetched with 'playwright install --with-deps chromium' into
PLAYWRIGHT_BROWSERS_PATH=/usr/local/share/ms-playwright — a system path that
the /home/developer volume can't shadow (unlike agent-browser's own
~/.agent-browser/browsers default, which WOULD vanish on recreate).
- Stable /usr/local/bin/agent-chrome symlink, exported as
AGENT_BROWSER_EXECUTABLE_PATH, insulates the ENV from Playwright's
per-version chromium-<rev> dir name.
Verified end-to-end (this session): agent-browser drives the baked Chromium
headless (open + title + screenshot + eval into a WebGL SPA); doctor launch
test passes in ~0.5s. Debian trixie --with-deps resolution verified (exit 0;
t64 lib renames handled). hadolint clean; check-base-hash OK (only *_VERSION
args added). Cost ~960 MB (Chromium + headless shell). Base-affecting →
rebuilds base-<hash> on next release.
Accented filenames on a macOS host are stored decomposed (NFD), so a precomposed
(NFC) remote path in scp/dscp silently fails with 'No such file or directory'.
Document the wildcard / list-first workaround in the pi-devbox-environment skill,
next to the dssh/dscp alias table. (Hit while copying a screenshot named
'Skärmavbild ….png' from the host.)
Wraps /etc/pi-devbox/build-manifest.json (already written at docker-build
time in Dockerfile.variant) into a human-readable summary instead of
requiring users to know the manifest path and pipe it through jq
themselves.
- rootfs/usr/local/bin/pi-devbox-version: human (default) / --json /
--quiet output modes. Also flags live drift — compares the baked
pi_version against the actually-running `pi --version` and warns
on mismatch rather than trusting the manifest blindly (same
ground-truth philosophy as the manifest generation itself). Exits 1
with a short stderr notice on images built before the manifest
existed, instead of failing silently.
- entrypoint-user.sh: calls it as the very first line. CMD is
`bash -l` with tty:true in compose, so this banner lands directly
above the first prompt on container start — no separate motd/bashrc
hook needed (deliberately not wired into .bash_aliases, which would
reprint on every docker exec -it).
- Dockerfile.base: COPY + chmod, same pattern as dot-watch/studio-expose.
- scripts/smoke-test.sh: 4 new checks (binary present+executable, human
output has release tag, --json round-trips the manifest, --quiet is
a single line).
- README.md / AGENTS.md / CHANGELOG.md updated.
Roll the 7 unreleased commits since v1.4.0 into v1.5.0 (minor — significant
base additions: readable Neovim true-colour, broad terminal terminfo support).
Also includes: typst PDF template font fix, .claude gitignore seed, pi-studio
semver-tag pin + version label, and repo hygiene (LICENSE, THIRD_PARTY.md,
.dockerignore, hadolint lint, IDEAS backlog). No pi bump — CI resolves latest
pi from npm at build time as usual.
The base only shipped ncurses-base (xterm-256color, tmux), so SSHing in from
WezTerm/Alacritty/foot/Ghostty degraded to a dumb TERM fallback. Following the
maintainer's ansible common role:
- Dockerfile.base: install ncurses-term (terminfo for wezterm, alacritty,
foot, st, base ghostty entry, and many more).
- rootfs/.../terminfo-src/ghostty.terminfo: thin xterm-ghostty alias
(use=ghostty) — Ghostty connects as TERM=xterm-ghostty, which no distro
packages. Compiled into the system db with 'tic -x'; build asserts it
resolved via infocmp.
- kitty (xterm-kitty) already covered by kitty-terminfo; iTerm2 uses
xterm-256color (ncurses-base).
- smoke-test: assert ncurses-term emulators + xterm-ghostty alias resolve.
Validated end-to-end in a throwaway container: ncurses-term brings the
entries, the alias compiles and equals the ghostty capability set. hadolint
clean, bash -n OK. Base-affecting, rebuilds base-<hash>. No tag.
Repo/CI hygiene batch (none base-affecting; image contents unchanged):
- LICENSE: actual MIT file (repo previously declared MIT only in prose).
- THIRD_PARTY.md: notes bundled software + licenses (pi/pi-fork/pi-obsmem/
pi-studio MIT, gosu Apache-2.0, Debian packages under their own terms).
- .dockerignore: trims build context to what the Dockerfiles COPY (rootfs/ +
entrypoint*.sh); keeps .git/docs/scripts/compose out. Verified it excludes
none of the required COPY sources.
- lint.yml: new hadolint job (pinned v2.14.0) lints both Dockerfiles;
.hadolint.yaml grandfathers deliberate choices (DL3008/DL3016/DL4006/DL3003/
SC2086, mirroring the shellcheck excludes), fails on anything new at warning+.
Verified hadolint exit 0 and the repo shell-guard passes with the new job.
- IDEAS.md: parks deferred follow-ups (SHA-pin actions, trivy, buildx SBOM/
provenance, Makefile, renovate).
- README/DOCKER_HUB License sections now link LICENSE + THIRD_PARTY.md.
No tag.
Upstream omaclaren/pi-studio stopped publishing GitHub Releases at v0.5.55
but keeps tagging every version (v0.9.36 now) and pushing to main. Tracking
main HEAD risked baking half-finished commits that land after a tag.
resolve-versions now lists all tags via a single git ls-remote (the REST
tags API paginates at 100 and the repo has >140 tags, so a single page can
miss the newest), picks the highest X.Y.Z with sort -V (pre-releases
excluded), and peels it to a commit SHA for PI_STUDIO_REF. SHA (not moving
tag) keeps cache-busting + reproducibility; require_sha still enforced.
Studio-variant only, not base-affecting. No change to the resolved commit
today (v0.9.36 == current main HEAD). Validated: yq parse OK, bash -n OK,
live git ls-remote -> v0.9.36 -> 2ef38ef. No tag.
Vanilla Neovim fell back to a 256-colour palette over ssh/kitty and rendered
strings/comments in a muddy, low-contrast colour. Fix, all base-affecting:
- rootfs/etc/xdg/nvim/sysinit.vim: system vimrc enabling termguicolors. Loads
for every user before any personal ~/.config/nvim; overridable per-user.
- Dockerfile.base: install kitty-terminfo (TERM=xterm-kitty understood; lets
Neovim auto-detect true colour) and set ENV COLORTERM=truecolor.
- smoke-test.sh: assert xterm-kitty terminfo present and nvim tgc default on.
- CHANGELOG (Unreleased/Added) + README editor note.
No tag — lands on the next base-<hash> rebuild.
Claude Code's per-machine local settings can carry credentials and should never
be committed. Add the pattern to the seeded ~/.gitignore_global so fresh
containers match a host global that already ignores it. Existing containers are
unaffected (seed copied only when absent). Base-affecting; rebuilds base-<hash>.
pandoc's bundled typst template defaults the document font to an empty
tuple (font: ()), so a naked `pandoc --pdf-engine=typst` fails with
"font fallback list must not be empty". Patch the template default to
Libertinus Serif (typst's own bundled default) at build time so PDF
export works out of the box. Document usage in README and note the fix
in CHANGELOG (Unreleased). Base-affecting.
Finalize the Unreleased batch as v1.4.0 (minor — significant base
additions). Base rebuilds (Dockerfile.base for typst/xz-utils;
.bash_aliases for the SSH check). pi auto-resolves latest (0.80.3 ->
0.80.6); mempalace stays 3.5.0 (current).
pandoc has been in the base since v1.0.0 but only as a front-end; PDF
export (studio_export_pdf / pandoc -o out.pdf) failed with 'xelatex not
found' because no back-end engine was installed. Ship typst (~30 MB
static Rust binary, no LaTeX) as the default engine via
`pandoc --pdf-engine=typst`, chosen over a ~600 MB TeX Live install.
texlive-xetex remains the higher-fidelity install-on-demand fallback.
- Dockerfile.base: install typst (latest GitHub-release idiom, pin via
TYPST_VERSION); add xz-utils (typst ships .tar.xz); bump
BASE_REBUILD_DATE. Lands in base-<hash>.
- smoke-test.sh: verify typst + a real pandoc --pdf-engine=typst render.
- README/AGENTS/CHANGELOG: typst now shipped (supersedes the planned
:latest-studio-tex variant).
No tag pushed — CI build intentionally deferred.
Adds a _devbox_check_host_ssh() check to ~/.bash_aliases (baked into
the image). On first bash session of each container it tries a quick
SSH probe to the Mac host; if it fails it prints a clear one-time
warning with the exact two steps needed to fix it:
1. Enable Remote Login in macOS System Settings
2. echo '<public key>' >> ~/.ssh/authorized_keys
The check is guarded:
- only runs inside a container (/.dockerenv)
- only when the jump key exists (~/.ssh-local/devbox_jump_ed25519.pub)
- only once per container lifetime (/tmp flag, cleared on recreate)
After --force-recreate the key changes, the flag is gone, and the
check runs again on the first bash window. Subsequent windows are
silent.
PDF export from Studio/pandoc still isn't shipped. Record the engine decision
in the living docs (README + AGENTS): pandoc is in the image but has no PDF
back-end, so export fails with 'xelatex not found'. Prefer a lightweight engine
— typst (~30 MB static binary, 'pandoc --pdf-engine=typst') which is small
enough it could ship in base rather than needing a separate ':latest-studio-tex'
variant; texlive-xetex (~600 MB) kept as the higher-fidelity fallback. Also drop
stale 'v1.3.0' pins (v1.3.0 already shipped without PDF) in favour of 'a future
release'. CHANGELOG history left untouched.
Wire the shared-palace option (implemented in mempalace-toolkit's mempalace.ts)
into the container:
- .env.example: document MEMPALACE_REMOTE_URL / MEMPALACE_REMOTE_TOKEN (env_file-only,
per this repo's convention).
- docker-compose.mempalace.yml: optional shared server (mempalace-mcp --transport http),
loopback-bound by default.
- docker-compose.yml: local-vs-external note on the palace-volume comment.
- README + CHANGELOG (Unreleased).
The image shipped only nvim (EDITOR=nvim), a modal vi-style editor. Not
everyone is comfortable with vi keybindings, so add both a classic and a
modern non-modal option:
- nano (apt): ~2.8 MB installed; deps (libc6, libncursesw6, libtinfo6)
already present via nvim/less/htop/tmux, so no extra packages pulled in.
- micro: ~12 MB single static Go binary from GitHub releases (same pattern
as bat/eza/zoxide). Desktop-style keys (Ctrl+S/Ctrl+Q), mouse, syntax
highlighting. ARG MICRO_VERSION pins; defaults to latest.
Combined ~15 MB (<0.5% of the ~3.2 GB image). EDITOR stays nvim; both new
editors are opt-in (export EDITOR=micro | nano). Uses the canonical
micro-editor/micro URL because the old zyedidia/micro org rename makes
/releases/latest redirect to another /latest, defeating the tag-parsing
latest-resolution idiom.
Base-image change, so it lands on the next base-<hash> rebuild. Updates
README tool table + EDITOR note, CHANGELOG (Unreleased/Added), and
smoke-test.sh (nano + micro presence checks).
actionlint's no-arg project auto-detection looks for .github/workflows
and hard-fails (exit 3, 'no project was found') on this .gitea/workflows
layout — observed on run 420. Glob the workflow files explicitly. The
Gitea shell guard step already passed in that run; only the actionlint
invocation needed the path fix.
Root cause of the recurring 'Illegal option -o pipefail' failures
(ed49b8d resolve-versions; b7197e8 promote-base-latest, run 418):
docker-publish.yml had no workflow-level default shell, so Gitea's
sh/dash default applied and every bash-syntax step had to individually
remember 'shell: bash'.
- docker-publish.yml: add 'defaults: run: shell: bash' — fixes the whole
class; all pre-existing dash steps are POSIX so bash runs them unchanged.
- lint.yml: new workflow, runs on every push/PR (not just release tags):
* scripts/check-workflow-shell.sh — Gitea-accurate guard: fails if any
run: step doesn't resolve to bash. Catches the omit-shell+bash-syntax
case that actionlint MISSES (actionlint models GitHub, where the
default shell is bash, so a shell-less step is assumed bash).
* actionlint + shellcheck — catches explicit 'shell: sh' + bash syntax
(SC3040) and general workflow errors.
Verified locally: guard + actionlint pass current workflows; guard fails
a synthetic omit-shell+pipefail workflow; shellcheck clean.
b7197e8 moved the digest-compare into the re-tag step with 'set -euo
pipefail' but no 'shell: bash'; Gitea's default sh (dash) aborts on
-o pipefail, leaving base-latest un-promoted on the v1.2.4 release
(run 418). Same footgun as ed49b8d. Consumer tags unaffected (they
FROM base-<hash>, not base-latest).
Seed ~/.gitignore_global from /etc/skel-devbox (seed-if-absent, like
.bash_aliases/.inputrc, so user edits survive recreate) and wire it via
git config --global core.excludesFile, guarded so a user-set excludesFile
is never overridden. Ignores *.bak, *.bak.*, *~, *.orig, *.swp, *.tmp
across all repos without per-repo .gitignore entries.
Removes GITEA_ACCESS_TOKEN / GITEA_HOST / GITHUB_PERSONAL_ACCESS_TOKEN from
the compose environment: block. An environment: entry both overrides
env_file AND is interpolated from the host shell, so a stale shell export
(e.g. one auto-loaded by an opencode/dotenv hook) silently shadowed the
users .env — an updated token never reached the container. Secrets now flow
solely via env_file: .env; .env.example already documents every variable.
- docker-compose.yml: drop the 3 passthrough lines + explanatory comment
- README.md: sync the "basic shape" snippet
- CHANGELOG.md: note under Unreleased (no tag bump / unpublished)
The gate keyed off need_build=='true', assuming need_build==false meant
base-latest was already current. A dry-run dispatch (promote_latest=false)
that pre-builds base-<hash> falsifies that: the later tag run sees
need_build==false and skipped promotion, leaving base-latest one base
behind (observed 2026-06-27, v1.2.3 dry-run-first release).
Gate now runs on every tag release / promote dispatch; the no-op
optimization moved into the step as a crane digest compare so it re-tags
only when base-latest actually differs from the released base-<hash>.
Workflow-only change; base hash unaffected (no base rebuild).
Patch release. Headline: mempalace-mcp self-heals instead of latching
available=false permanently after a slow virtiofs cold-open. Base image
rebuilds via the mempalace-toolkit ref advancing to e12b624 (folded into
the base-decide hash). No pi/mempalace version change — pi npm latest is
still 0.80.2 (= v1.2.2). Also releases the queued yq (mikefarah Go yq) and
mempalace-skill temporal-grounding changes.
mempalace.ts now self-heals (respawn with capped backoff) instead of
latching unavailable, and the init-timeout default is 300000. Update the
explanatory comment + tunable list (MEMPALACE_MCP_MAX_RESPAWNS,
MEMPALACE_MCP_RESPAWN_BACKOFF_MS). Comment-only; no build/ENV change.
Baked mempalace SKILL.md now instructs agents to establish current date/time
and compute the delta against the actual diary/drawer timestamp before using
relative terms (yesterday/last week), and explicitly that a container recreate
or fresh session is NOT a day boundary (pi-devbox restarts several times a day).
Phase 1 wake-up section + anti-pattern bullet. CHANGELOG Unreleased.
Match the repo's latest-following convention (tealdeer/uv/etc.) and keep the
container in sync with the Mac's brew yq. smoke-test now asserts mikefarah AND
major v4, so a surprise yq v5 fails CI instead of silently breaking
provision.sh. Pin still available via --build-arg YQ_VERSION=vX.Y.Z.
Debian/Ubuntu `apt install yq` is kislyuk/yq (Python, v3.x), incompatible
with the mikefarah v4 syntax the cloud-init repo's provision.sh/deploy.sh
require. Replace the apt package with a pinned mikefarah Go binary, mirroring
the existing tealdeer ARG (latest-or-pin) pattern, multi-arch amd64/arm64.
smoke-test.sh now asserts `yq --version` reports mikefarah so CI catches a
regression. CHANGELOG: Unreleased entry.
- Bump mempalace pin 3.4.0 -> 3.5.0: 3.5.0 carries the upstream fix for the
top-level-anyOf diary_write schema (issue #1728 / PR #1717, merged
2026-06-14). Verified against the published 3.5.0 wheel that mcp_server.py
now advertises 'required: [agent_name]' with no root-level anyOf.
- Remove the Dockerfile.base perl workaround that stripped the anyOf from the
installed mcp_server.py — obsolete now the fix is upstream.
- pi auto-resolves to npm latest (0.79.10 -> 0.80.2) at build.
- CHANGELOG v1.2.2.
Bake pi-extensions + mempalace skills into the image (available without a
mounted skillset) and add the mempalace session-start proactive-load directive
so frequently-recreated containers actually pick the skill up. Closes the
fork/recall + mempalace under-utilisation gap.
CHANGELOG: [Unreleased] -> v1.2.1.
Baking the mempalace fallback skill fixed *availability*, but mempalace had
no proactive-load directive anywhere (pi-toolkit's global AGENTS.md only
points to pi-extensions), so a new container would still surface it only via
description-matching — the same under-utilisation the pi-extensions directive
was created to fix.
Add a session-start pointer to the pi-devbox managed AGENTS.md block
(pi-global-AGENTS.append.md): gated to pi-devbox containers and conditional on
the MemPalace MCP tools being present. Memory continuity matters most in a
frequently-recreated container — the palace is its only cross-recreate memory.
- pi-global-AGENTS.append.md: '## Session start: load the mempalace skill'.
- smoke-test: assert the pointer merges into the global AGENTS.md at build.
- docs: VENDORED.md, README, CHANGELOG [Unreleased].
Now both skills are complete in pi-devbox: directive + skill file.
pi-extensions = directive (pi-toolkit) + baked skill; mempalace = directive
(this block) + baked skill.
The pi-toolkit global AGENTS.md tells every pi session to read
~/.agents/skills/pi-extensions/SKILL.md at start (the fork/recall
under-utilisation fix), but that skill lived only in the private skillset
repo — so the pointer dangled in any container started without skillset
mounted. Bake fallbacks so the pointer always resolves.
- pi-extensions (Option 1 + Option 2, layered):
* Canonical skill promoted to the public pi-extensions package repo under
skill/ (separate commit there); co-located with the code it documents.
* rootfs/ carries a committed snapshot (the floor).
* Dockerfile.variant copies /opt/pi-extensions/skill/ over the snapshot
after the pinned clone, so a normal build ships the fresh package copy
(recorded via PI_EXTENSIONS_REF) and an old-ref/mirror build still ships
the snapshot. Helper evaluate-extension-usage.py travels with it.
- mempalace (Option 2 only): snapshot in rootfs/. Its consumer skill has no
public package home (mempalace-toolkit ships a different skill,
opencode-mempalace-bridge), so no build-time refresh.
- entrypoint links both (only-when-absent; mounted skillset still wins).
- smoke-test: build-time presence + package-match check + runtime symlink
assertions; readiness gate now waits on the last-linked skill.
- docs: skills/VENDORED.md (provenance + refresh), README, AGENTS.md,
CHANGELOG [Unreleased].
Note: shipped in the NEXT release; v1.2.0 (run 409) predates this.
The runtime 'pi-devbox-environment skill linked' smoke assertion failed in
CI run 408 (gating build-variant). Root cause: the skill-linking block ran
AFTER the pi-toolkit/extensions deploy, but the smoke readiness gate only
waits on pi-deploy markers (keybindings.json, mempalace.ts) — which land
before the skill symlink — so the assertion sampled too early.
- entrypoint-user.sh: move the image-baked-skills symlink loop to run early
(before the pi deploy block), so it completes before any readiness marker.
Still before the skillset deploy, so foreign-link semantics are unchanged.
- smoke-test.sh: add the skill symlink to the readiness gate as well.
Build-time checks (baked skill, append snippet, merged AGENTS marker) all
passed in 408; only the timing of the runtime check was wrong.
Ship skills inside the image (independent of any mounted skillset repo):
- rootfs/usr/local/share/pi-devbox/skills/<name>/ symlinked into
~/.agents/skills/ by entrypoint-user.sh (foreign-link, survives volume
recreate, never clobbers a skillset/user skill of the same name).
- New pi-devbox-environment skill: persistence model, host/LAN SSH
reachability, split-DNS mechanisms, interactive-vs-tool-shell alias
gotcha, tmux 0-index, uv-first Python, pi-studio reachability. Agnostic
to host OS / hostnames / domains / nameservers (discovered at runtime).
- Dockerfile.variant appends pi-global-AGENTS.append.md onto pi-toolkit's
pi-global-AGENTS.md (single global slot) so the skill is loaded
proactively; gated on /usr/local/lib/pi-devbox/. Idempotent.
- smoke-test: baked-skill + append-snippet + merged-marker presence and a
runtime symlink assertion.
- docs: README 'Agent skills' section, AGENTS.md layout, DOCKER_HUB.md;
moved studio-tex roadmap to v1.3.0.
pi 0.79.7 -> 0.79.10 (auto-resolved from npm latest at build).
The host-owned, bind-mounted ~/.config/devbox-shell/ssh-lan.conf is the
intended place to add `ProxyJump host` overrides for named LAN peers (so
`pi --ssh <peer>` / `dssh <peer>` route through the host), but it was only
documented in .env.example and the setup-lan-access.sh header — never in the
README, where someone hitting "can't reach LAN peers" actually looks.
- README: add a "Naming LAN peers" subsection under the macOS LAN-peers
troubleshooting block, with a ProxyJump example and the read-only ~/.ssh
caveat; add a pointer to it from the SSH and ControlMaster section.
- setup-lan-access.sh: correct the INCLUDE_BLOCK comment that suggested adding
ProxyJump to the read-only ~/.ssh/config; point at ssh-lan.conf instead.
- CHANGELOG: note under Unreleased.
Docs/comment only — no behavior change.
The default run shell is 'sh -e {0}' (dash on the act runner), which
rejects 'set -o pipefail' ('Illegal option -o pipefail') — failing the
resolve-versions job on line 2 and cascading every dependent job to
skipped (v1.1.6 run 401). The heavy build steps already declare
'shell: bash'; the resolve step did not. Added it.
Adds OCI labels + /etc/pi-devbox/build-manifest.json so a published tag is
self-describing and reconstructable after CI logs rotate (manifest is
written from the actual checked-out HEAD of each /opt clone + live
pi --version, not just the intended build-args).
Hardens the build plumbing:
- scripts/check-base-hash.sh guards the base-rebuild invariant: every
floating ARG *_REF in Dockerfile.base must be folded into the base_tag
hash, else a ref-only change silently fails to rebuild the base
(v1.1.2-class staleness footgun). Runs in base-decide and locally.
- resolve-versions now fails loud instead of falling back to a floating
main/master on a transient API failure — validates each ref is a 40-hex
SHA (and pi a real semver) and aborts the release otherwise.
- The three gitea companions (pi-toolkit, pi-extensions, mempalace-toolkit)
gained overridable *_REPO build-args (defaulting to the canonical gitea
origin) so a relocated/forked build can repoint them without editing the
Dockerfiles — matching the existing PI_FORK_REPO/PI_OBSMEM_REPO pattern.
README documents the forked/relocated build-arg trick and how to read the
labels + manifest. smoke-test asserts the manifest + labels. pi bumps
0.79.7 → 0.79.8 (auto-resolved at build).
Coordinated with the pi-extensions ssh-controlmaster fix (picked up at build via
PI_EXTENSIONS_REF=main), this makes `pi --ssh <host>` and `dssh`/`dscp` robust
to a user ~/.ssh/config whose per-host ControlPath points under the read-only
~/.ssh bind-mount (e.g. `ControlPath ~/.ssh/cm/%r@%h:%p`). A system default can
never override a user's per-host value, so the fix lives in two layers.
- setup-lan-access.sh: always render the writable ~/.ssh-local/config sidecar
(Host * ControlPath redirect into ~/.ssh-local/cm + Include ~/.ssh/config) on
EVERY host OS. Previously the script exited early (no-op) on native Linux,
leaving dssh/dscp broken when ~/.ssh was read-only there too. The host-jump
block, its key generation, and the authorize hints stay gated on VM-backed
detection / DEVBOX_LAN_ACCESS=jump (new NEED_JUMP flag).
- Dockerfile.base: document that the /etc/ssh drop-in default cannot override a
user per-host ControlPath; cross-ref the two handling layers.
- entrypoint-user.sh: correct the now-stale "no-op on native Linux" comment.
- README.md / DOCKER_HUB.md: document read-only-~/.ssh ControlPath handling.
CHANGELOG: v1.1.5 (Fixed + Changed + pi 0.79.6 -> 0.79.7 auto-resolved bump).
The settings.json bootstrap only fires when the file is ABSENT, so a
settings.json on a preserved named volume never picks up config added in a
later image (e.g. the observational-memory / pi-fork blocks, a newly-enabled
model). Users had to hand-merge after every upgrade.
On start, when settings.json already exists, deep-merge the template into it
with 'jq -s ".[0] * .[1]"' (template first, live second) so the user's values
always win and only MISSING keys are filled from the template. Arrays are
leaves (a model the user removed is not re-added). Rewrites only when the
merge changes something, backs up the original first, and skips safely (no
clobber) if either file is invalid JSON. Opt out with PI_SETTINGS_MERGE=0.
Add a recreate-sanity-check assertion that settings.json carries the
observational-memory + pi-fork blocks after recreate.
The history-flush guard was exported, so it leaked into child processes.
Any nested shell -- crucially each tmux pane (which inherits the tmux
server's env) -- then saw the guard already set and skipped installing
'history -a' in PROMPT_COMMAND. Those shells only persisted history on a
clean exit, so abrupt termination (docker stop, tmux kill-server, SIGKILL)
silently lost their in-memory history. zoxide was less affected (its hook
is installed unguarded and writes immediately).
Make the guard shell-local (drop 'export') so every new interactive shell
re-installs its own per-prompt flush. Add a recreate-sanity-check assertion
that a nested login shell still wires up 'history -a'.
Storage was never the issue: ~/.cache/bash (devbox-shell-history) and
~/.local/share/zoxide (devbox-zoxide) are both persistent named volumes.
pi-toolkit now symlinks pi-global-AGENTS.md -> ~/.pi/agent/AGENTS.md (pi's
global-instructions file, loaded at every start; directs the agent to read
the pi-extensions skill at session start). Add a recreate-sanity-check
assertion alongside the keybindings symlink check so a future image build
that bakes the new pi-toolkit verifies the wiring landed.