docs: correct the "pi-studio has no bind flag" claim everywhere it was baked
Lint / hadolint (push) Successful in 10s
Lint / skill-floor (push) Successful in 14s
Lint / doc-drift (push) Failing after 18s
Lint / actionlint (push) Successful in 31s

1e4c238 fixed README; the same claim lived in three more places, all wrong
since pi-studio 0.9.60 added /studio --listen-all:

- rootfs/usr/local/bin/studio-expose header (and so its --help; the sed range
  grows 2,31p -> 2,35p so --help still ends at "runs it backgrounded.")
- pi-devbox-environment skill section 7: --listen-all as a third path, plus the
  0.10 whole-URL / 409 / "link looks damaged" notes, the published-port rule,
  --stop before --port, and the on-host curl 403-vs-empty-reply diagnostic
- Dockerfile.variant STUDIO_PORT comment

Bridge and --listen-all on one port: whichever starts second fails, measured
both ways on tor-ms22 (v1.11.2): Studio "listen EADDRINUSE ... 0.0.0.0:8765"
with the bridge up; studio-expose rc 1 on socat "bind(...): Address already in
use" with a 0.0.0.0 listener up. README's warning now says both directions.

Cost: studio-expose and the skill are in rootfs/, which base_tag hashes, so the
next release rebuilds the base (recomputed with the CI recipe: base-6f3b20266de1
at 1e4c238 -> base-26d425cea6c2 now). Noted in the CHANGELOG Unreleased entry.
No pin changes. lint-shell, dockerfile-directives and base-hash guards pass;
check-doc-drift is red only for the unrelated pi-extensions 750b2a8 -> 0d8e417.
This commit is contained in:
2026-10-14 19:27:42 +02:00
parent 1e4c238a83
commit a8f5b35d2a
5 changed files with 56 additions and 25 deletions
+16 -6
View File
@@ -13,18 +13,28 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
## Unreleased
Docs only: this entry changes no pin.
Docs and comments only: this entry changes no pin. Two of the files are
baked into the base image (`rootfs/usr/local/bin/studio-expose` and the
`pi-devbox-environment` skill), and `base_tag` hashes all of `rootfs/`, so
**the next release rebuilds the base** rather than reusing
`base-6f3b20266de1`.
### Fixed
- **README "Using pi-studio" said pi-studio has no bind flag.** It has had
`/studio --listen-all` (binds `0.0.0.0`) since pi-studio 0.9.60, so the
section now lists it as path **C**, next to host networking (A) and the
`studio-expose` bridge (B). It also warns not to combine B and C on one port.
With `STUDIO_EXPOSE=1` the bridge holds the egress IP on `$STUDIO_PORT`
from boot, and `/studio --listen-all --port 8765` then fails with
`listen EADDRINUSE: address already in use 0.0.0.0:8765` (measured on
tor-ms22, v1.11.2).
`studio-expose` bridge (B). It also warns not to combine B and C on one
port, because whichever starts second fails. With `STUDIO_EXPOSE=1`,
`/studio --listen-all --port 8765` fails with
`listen EADDRINUSE: address already in use 0.0.0.0:8765`. In the other
order, `studio-expose` exits 1 on socat's `bind(…): Address already in
use` (both measured on tor-ms22, v1.11.2). The same stale "no bind
flag" claim is corrected in the three other places that carried it:
the `studio-expose` header (and so its `--help`, whose `sed` range
grows from `2,31p` to `2,35p`), §7 of the baked `pi-devbox-environment`
skill (which also gains the whole-URL, published-port and `curl` notes
below), and the `STUDIO_PORT` comment in `Dockerfile.variant`.
- **The remote recipe described a pi-studio 0.9 URL.** Since 0.10 the printed
URL carries `studioTabState` and `hostedWorkspace` as well as the token, and
a token-only URL is answered `409`. The recipe now says to open the whole
+5 -4
View File
@@ -676,10 +676,11 @@ RUN if [ "${INSTALL_STUDIO}" = "true" ]; then \
# STUDIO_PORT: advisory default consumed by docker-compose port publishing
# and the recommended `/studio --no-browser --port "$STUDIO_PORT"` launch.
# Harmless in the non-studio variant. NOTE: pi-studio hard-binds the server
# to 127.0.0.1 inside the container (index.ts: .listen(port,"127.0.0.1")),
# so reaching it from a browser needs a loopback bridge or host networking —
# see the "Using pi-studio" section in README.md.
# Harmless in the non-studio variant. NOTE: by default pi-studio binds the
# server to 127.0.0.1 inside the container, so reaching it from a browser
# needs host networking, the studio-expose loopback bridge, or the opt-in
# `/studio --listen-all` (pi-studio >= 0.9.60; never together with the
# bridge on one port) — see the "Using pi-studio" section in README.md.
ENV STUDIO_PORT=8765
# ── Optional: Go toolchain ───────────────────────────────────────────
+5 -4
View File
@@ -261,10 +261,11 @@ publish. Keep the publish on `127.0.0.1:` as in B: anyone who can reach the
port and has the full URL controls Studio, including running prompts and
reading or writing files, and there is no TLS.
> **Use B or C on a port, not both.** With `STUDIO_EXPOSE=1` the bridge holds
> the container's egress IP on `$STUDIO_PORT` from boot, and `--listen-all` on
> that same port fails with `EADDRINUSE` (`0.0.0.0` includes that address).
> If the bridge is running, start Studio *without* `--listen-all`.
> **Use B or C on a port, not both.** Whichever starts second fails with
> `EADDRINUSE`: `0.0.0.0` includes the egress IP the bridge binds. With
> `STUDIO_EXPOSE=1` the bridge holds `$STUDIO_PORT` from boot, so
> `/studio --listen-all --port 8765` fails. If the bridge is running, start
> Studio *without* `--listen-all`.
### Remote host (SSH / mosh)
+11 -7
View File
@@ -3,13 +3,17 @@
# through a published Docker port.
#
# WHY THIS EXISTS
# pi-studio hard-binds its HTTP/WebSocket server to 127.0.0.1 inside the
# container (index.ts: `.listen(port, "127.0.0.1")`) and there is no
# --host / bind flag. A plain `docker run -p 8765:8765` forwards to the
# By default pi-studio binds its HTTP/WebSocket server to 127.0.0.1 inside
# the container. A plain `docker run -p 8765:8765` forwards to the
# container's EXTERNAL interface (eth0), not its loopback, so it cannot
# reach Studio. This helper runs a socat TCP relay that listens on the
# container's egress IP and forwards to 127.0.0.1:<port>, so a published
# port (and an `ssh -L` tunnel from your laptop) can reach Studio.
# reach a default Studio. This helper runs a socat TCP relay that listens
# on the container's egress IP and forwards to 127.0.0.1:<port>, so a
# published port (and an `ssh -L` tunnel from your laptop) can reach Studio.
#
# The alternative (pi-studio >= 0.9.60) is `/studio --listen-all`, which
# binds 0.0.0.0 and needs no bridge. Use one or the other on a port, never
# both: whichever starts second fails with EADDRINUSE (0.0.0.0 includes
# the egress IP this relay binds).
#
# SECURITY
# This intentionally exposes Studio beyond loopback — anything that can
@@ -34,7 +38,7 @@ set -euo pipefail
PORT="${1:-${STUDIO_PORT:-8765}}"
if [ "$PORT" = "--help" ] || [ "$PORT" = "-h" ]; then
sed -n '2,31p' "$0" | sed 's/^# \{0,1\}//'
sed -n '2,35p' "$0" | sed 's/^# \{0,1\}//'
exit 0
fi
@@ -356,8 +356,8 @@ window: 0". Leave the indexing alone in this environment.
## 7. pi-studio reachability (only in the `-studio` variant)
Present only if `/opt/pi-studio` exists / the `studio_*` tools are in your tool
list. pi-studio **binds to `127.0.0.1` inside the container** with no host-bind
flag, so a plain `docker -p` publish can't reach it. Two supported paths:
list. By default pi-studio **binds to `127.0.0.1` inside the container**, so a
plain `docker -p` publish can't reach it. Three supported paths:
- **Host networking** (`network_mode: host`): container loopback == host
loopback; open the tokenized URL on the host. (Changes
@@ -365,10 +365,25 @@ flag, so a plain `docker -p` publish can't reach it. Two supported paths:
- **`studio-expose` bridge** (`STUDIO_EXPOSE=1` or run `studio-expose &`): a
`socat` relay from the container's external interface to its loopback, so a
published `127.0.0.1:PORT` + `ssh -L PORT:127.0.0.1:PORT host` reaches it.
- **`/studio --listen-all`** (pi-studio ≥ 0.9.60): binds `0.0.0.0`, so the
published port reaches it with no bridge; the printed URL still says
`127.0.0.1`. **Never on the same port as the bridge**: whichever starts
second fails with `EADDRINUSE`. `ss -ltnp | grep ":$STUDIO_PORT"` showing a
`socat` line means the bridge is up, so start Studio without `--listen-all`.
The real auth token comes from the `/studio` slash command (`/studio --status`
to reprint), **not** from `studio-expose`. For Graphviz, use `dot-watch` →
PNG (Studio renders Mermaid natively and previews PNG, but not SVG/DOT).
to reprint), **not** from `studio-expose`. Hand over the **whole** printed
URL: since pi-studio 0.10 it also carries `studioTabState` + `hostedWorkspace`
(or `docPath`/`watchFile`/… for `--watch`), and a token-only URL is answered
`409`. The URL wraps across TUI lines; if a side panel's text lands inside the
copy, Studio answers *"This link looks damaged…"*. Studio must run on the
**published** port (only the laptop side of `ssh -L` is free to choose), and
`--port` on a running server only warns, so `/studio --stop` first. On the
Docker host, `curl -s http://127.0.0.1:$STUDIO_PORT/` answering `403` means
the chain is up; an empty reply means Studio is down or on another port.
For Graphviz, use `dot-watch` → PNG (Studio renders Mermaid natively and
previews PNG, but not SVG/DOT).
## 8. MemPalace is the shared brain