diff --git a/js/docs/src/pages/back-to-home.astro b/js/docs/src/pages/back-to-home.astro
index af7773f9..c942f18f 100644
--- a/js/docs/src/pages/back-to-home.astro
+++ b/js/docs/src/pages/back-to-home.astro
@@ -17,6 +17,6 @@ const redirectUrl = hostname.endsWith('.stream.place')
window.location.href = redirectUrl;
-Redirecting to Streamplace...
+Redirecting to Streamplace...
Click here if you are not redirected automatically
--
2.51.2
From 990f19f5bc157a76b695bc1bb1a35a6213b94f02 Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Thu, 27 Aug 2026 16:35:25 -0700
Subject: [PATCH 02/10] ci: pin Node 22 via .node-version for Cloudflare Pages
docs build
Cloudflare Pages resolves engines.node '>=22.0.0' to the newest
available Node (now 24.x), where better-sqlite3@10.1.0 has no
prebuilds and fails to compile against V8. Pin 22 in-repo;
mise honors the same file.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NaDYk2PGWfSLEXeN5CBJFN
---
.node-version | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .node-version
diff --git a/.node-version b/.node-version
new file mode 100644
index 00000000..2bd5a0a9
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+22
--
2.51.2
From 8a62dd942aa4592f43316825a127b07b611ec46e Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Thu, 27 Aug 2026 16:45:07 -0700
Subject: [PATCH 03/10] docs: no-op change for CI
---
js/docs/src/pages/back-to-home.astro | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/js/docs/src/pages/back-to-home.astro b/js/docs/src/pages/back-to-home.astro
index c942f18f..af7773f9 100644
--- a/js/docs/src/pages/back-to-home.astro
+++ b/js/docs/src/pages/back-to-home.astro
@@ -17,6 +17,6 @@ const redirectUrl = hostname.endsWith('.stream.place')
window.location.href = redirectUrl;
-Redirecting to Streamplace...
+Redirecting to Streamplace...
Click here if you are not redirected automatically
--
2.51.2
From f718c6029ccb107597f9c400b429a441670d4e40 Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Thu, 27 Aug 2026 16:57:59 -0700
Subject: [PATCH 04/10] build: pin meson to 1.11.2
Meson 1.12.0 (released since the last builder-image rebuild) fails to
configure the glib 2.86.3 subproject ('Summary section already have
key', then libpcre2-8 fallback not found), breaking all binary builds.
Last green build on next used 1.11.2. Pin it in the builder image and
mise.toml until glib/gstreamer catch up.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NaDYk2PGWfSLEXeN5CBJFN
---
.ci/dockerfile-hash.yaml | 2 +-
docker/build.Dockerfile | 2 +-
mise.toml | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.ci/dockerfile-hash.yaml b/.ci/dockerfile-hash.yaml
index a923c418..5e27d0dc 100644
--- a/.ci/dockerfile-hash.yaml
+++ b/.ci/dockerfile-hash.yaml
@@ -1,2 +1,2 @@
variables:
- DOCKERFILE_HASH: a06ea168babbacb457bea7e6762aea47493f5097
\ No newline at end of file
+ DOCKERFILE_HASH: 28cc8e519521a047f1da9084773cbf08b0428ee7
\ No newline at end of file
diff --git a/docker/build.Dockerfile b/docker/build.Dockerfile
index a06ea168..28cc8e51 100644
--- a/docker/build.Dockerfile
+++ b/docker/build.Dockerfile
@@ -38,7 +38,7 @@ RUN apt update \
libc6:arm64 libstdc++6:arm64 \
cmake libssl-dev libssl-dev:arm64 \
ruby-rubygems postgresql sudo \
- && pip install meson tomli \
+ && pip install meson==1.11.2 tomli \
&& curl -L --fail https://go.dev/dl/go$GO_VERSION.linux-$TARGETARCH.tar.gz -o go.tar.gz \
&& tar -C /usr/local -xf go.tar.gz \
&& rm go.tar.gz
diff --git a/mise.toml b/mise.toml
index 107d7636..114a690f 100644
--- a/mise.toml
+++ b/mise.toml
@@ -2,7 +2,7 @@
go = "latest"
rust = "latest"
node = "22"
-meson = "latest"
+meson = "1.11.2"
ninja = "latest"
pnpm = "latest"
java = "17"
--
2.51.2
From 85dd86a6b74348702ecd51b601b8b24455032b92 Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Sat, 29 Aug 2026 12:59:29 -0700
Subject: [PATCH 05/10] ci: fix golangci-lint job hang (stdin redirect + job
timeout)
Since runner image 20260823, the golangci-lint-action's node-spawned
invocation of the podman-exec wrapper hangs with no output on its very
first call ('config path'), burning the full 360m job limit, while
bash-spawned podman execs in the same job work fine. Redirect stdin
from /dev/null in the wrapper so podman exec can't wait on the
action's inherited stdin pipe, add a bash-side sanity invocation
before the action to localize any future hang, and cap the job at 60m.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NaDYk2PGWfSLEXeN5CBJFN
---
.github/workflows/golangci-lint.yaml | 6 ++++++
hack/golangci-lint | 5 ++++-
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml
index 53d2fbd1..af7de5e6 100644
--- a/.github/workflows/golangci-lint.yaml
+++ b/.github/workflows/golangci-lint.yaml
@@ -8,6 +8,7 @@ jobs:
golangci:
name: lint
runs-on: ubuntu-latest
+ timeout-minutes: 60
steps:
- name: Maximize build space
run: |
@@ -33,6 +34,11 @@ jobs:
sudo ln -s $(realpath ./hack/golangci-lint) /usr/bin/golangci-lint
make golangci-lint-container BUILDER_TARGET=builder-no-darwin DOCKER_BUILD_OPTS="--layers --cache-to ghcr.io/streamplace/streamplace --cache-from ghcr.io/streamplace/streamplace"
+ - name: sanity-check wrapper from bash
+ run:
+ timeout 120 /usr/bin/golangci-lint config path
+ --config=./.golangci.yaml
+
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
diff --git a/hack/golangci-lint b/hack/golangci-lint
index 4e243231..c6f7988f 100755
--- a/hack/golangci-lint
+++ b/hack/golangci-lint
@@ -5,4 +5,7 @@
set -euo pipefail
-exec podman exec golangci-lint golangci-lint "$@"
+# stdin must not be a live pipe: when golangci-lint-action spawns this wrapper,
+# podman exec waits on the inherited stdin and hangs forever (observed on
+# runner image 20260823+); bash-spawned invocations were unaffected
+exec podman exec golangci-lint golangci-lint "$@" < /dev/null
--
2.51.2
From 78db03702e4effa51977a6e51a7868f8688adaeb Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Sat, 29 Aug 2026 14:02:37 -0700
Subject: [PATCH 06/10] ci: hang-proof the golangci-lint wrapper
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The action's node-spawned podman exec still hangs even with stdin
redirected (the bash sanity step running the identical command one
second earlier succeeds). Move podman's stderr to a file — removing
the action's pipe from podman's fd table — add a 3-minute timeout so
a hang fails fast, and dump podman's --log-level=debug trace on
failure to show where it wedges.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NaDYk2PGWfSLEXeN5CBJFN
---
hack/golangci-lint | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/hack/golangci-lint b/hack/golangci-lint
index c6f7988f..2adfaf13 100755
--- a/hack/golangci-lint
+++ b/hack/golangci-lint
@@ -3,9 +3,23 @@
# This script is used to trick the GitHub action for golangci-lint to run inside a container
# so that we can use the local build of streamplace
-set -euo pipefail
+set -uo pipefail
-# stdin must not be a live pipe: when golangci-lint-action spawns this wrapper,
-# podman exec waits on the inherited stdin and hangs forever (observed on
-# runner image 20260823+); bash-spawned invocations were unaffected
-exec podman exec golangci-lint golangci-lint "$@" < /dev/null
+# The golangci-lint-action's node-spawned invocations of podman exec hang
+# forever on runner images >= 20260823 while bash-spawned ones are fine
+# (compare the sanity-check step in golangci-lint.yaml). Point podman's
+# stderr at a file instead of the action's pipe, guard with a timeout so a
+# hang fails in minutes instead of hours, and dump podman's debug trace on
+# failure so we can see where it stuck.
+DBG="$(mktemp)"
+timeout -k 10 180 podman --log-level=debug exec golangci-lint golangci-lint "$@" "$DBG"
+code=$?
+if [ "$code" -ne 0 ]; then
+ echo "wrapper: podman exec exited $code; podman debug log follows" >&2
+ cat "$DBG" >&2
+else
+ # forward golangci-lint's own stderr, minus podman's debug chatter
+ grep -v '^time=' "$DBG" >&2 || true
+fi
+rm -f "$DBG"
+exit $code
--
2.51.2
From b1d449e70ff552c25233537bcf27c09f0fda90af Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Sat, 29 Aug 2026 14:26:45 -0700
Subject: [PATCH 07/10] ci: keep podman's stdio away from the action's
socketpairs
golangci-lint-action invokes commands via node child_process.exec;
its stdio pipes are libuv socketpairs, and podman exec wedges right
after startup when attached to them (bash-spawned runs of the same
command in the same job succeed). Buffer podman's stdout/stderr into
temp files and replay them, and on a residual hang SIGQUIT podman
for a goroutine dump before failing fast.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NaDYk2PGWfSLEXeN5CBJFN
---
hack/golangci-lint | 52 ++++++++++++++++++++++++++++++----------------
1 file changed, 34 insertions(+), 18 deletions(-)
diff --git a/hack/golangci-lint b/hack/golangci-lint
index 2adfaf13..f626d157 100755
--- a/hack/golangci-lint
+++ b/hack/golangci-lint
@@ -1,25 +1,41 @@
#!/bin/bash
-# This script is used to trick the GitHub action for golangci-lint to run inside a container
-# so that we can use the local build of streamplace
+# This script is used to trick the GitHub action for golangci-lint to run
+# inside a container so that we can use the local build of streamplace.
+#
+# The golangci-lint-action spawns this via node's child_process.exec, whose
+# stdio "pipes" are actually libuv socketpairs; podman exec wedges right
+# after startup when attached to them on runner images >= 20260823, while
+# bash-spawned runs of the identical command are fine. Keep podman's stdio
+# pointed at real files and replay them when it finishes. If podman still
+# hangs, SIGQUIT it for a Go goroutine dump and fail fast instead of eating
+# the job's timeout.
set -uo pipefail
-# The golangci-lint-action's node-spawned invocations of podman exec hang
-# forever on runner images >= 20260823 while bash-spawned ones are fine
-# (compare the sanity-check step in golangci-lint.yaml). Point podman's
-# stderr at a file instead of the action's pipe, guard with a timeout so a
-# hang fails in minutes instead of hours, and dump podman's debug trace on
-# failure so we can see where it stuck.
-DBG="$(mktemp)"
-timeout -k 10 180 podman --log-level=debug exec golangci-lint golangci-lint "$@" "$DBG"
-code=$?
-if [ "$code" -ne 0 ]; then
- echo "wrapper: podman exec exited $code; podman debug log follows" >&2
- cat "$DBG" >&2
-else
- # forward golangci-lint's own stderr, minus podman's debug chatter
- grep -v '^time=' "$DBG" >&2 || true
+OUT="$(mktemp)"
+ERR="$(mktemp)"
+trap 'rm -f "$OUT" "$ERR"' EXIT
+
+podman exec golangci-lint golangci-lint "$@" "$OUT" 2>"$ERR" &
+pid=$!
+
+seconds=0
+while kill -0 "$pid" 2>/dev/null && [ "$seconds" -lt 900 ]; do
+ sleep 1
+ seconds=$((seconds + 1))
+done
+
+if kill -0 "$pid" 2>/dev/null; then
+ echo "wrapper: podman exec still running after ${seconds}s; sending SIGQUIT for goroutine dump" >&2
+ kill -QUIT "$pid" 2>/dev/null || true
+ sleep 5
+ kill -KILL "$pid" 2>/dev/null || true
fi
-rm -f "$DBG"
+
+wait "$pid"
+code=$?
+
+cat "$OUT"
+cat "$ERR" >&2
exit $code
--
2.51.2
From 6c1abb12e6341adc97c460a3d9395960daa926d8 Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Sat, 29 Aug 2026 15:16:50 -0700
Subject: [PATCH 08/10] ci: identify the libpod lock holder when podman exec
hangs
Goroutine dump from the previous run shows the action-spawned podman
exec stuck 15 minutes in sem_wait on the rootless SHM container lock
inside ExecCreate. On hang, enumerate podman-family processes, list
who has the libpod lock segment mapped, and dump kernel stacks to
find the holder; fail after 5 minutes.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NaDYk2PGWfSLEXeN5CBJFN
---
hack/golangci-lint | 30 +++++++++++++++++++++---------
1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/hack/golangci-lint b/hack/golangci-lint
index f626d157..f2fad0e9 100755
--- a/hack/golangci-lint
+++ b/hack/golangci-lint
@@ -3,13 +3,11 @@
# This script is used to trick the GitHub action for golangci-lint to run
# inside a container so that we can use the local build of streamplace.
#
-# The golangci-lint-action spawns this via node's child_process.exec, whose
-# stdio "pipes" are actually libuv socketpairs; podman exec wedges right
-# after startup when attached to them on runner images >= 20260823, while
-# bash-spawned runs of the identical command are fine. Keep podman's stdio
-# pointed at real files and replay them when it finishes. If podman still
-# hangs, SIGQUIT it for a Go goroutine dump and fail fast instead of eating
-# the job's timeout.
+# On runner images >= 20260823, the action-spawned podman exec wedges in
+# sem_wait on the rootless SHM container lock (libpod LockSemaphore inside
+# ExecCreate) — some other process takes the lock and never releases it.
+# Buffer stdio through files, and on a hang dump diagnostics identifying
+# the lock holder, then fail fast instead of eating the job timeout.
set -uo pipefail
@@ -21,13 +19,27 @@ podman exec golangci-lint golangci-lint "$@" "$OUT" 2>"$ERR" &
pid=$!
seconds=0
-while kill -0 "$pid" 2>/dev/null && [ "$seconds" -lt 900 ]; do
+while kill -0 "$pid" 2>/dev/null && [ "$seconds" -lt 300 ]; do
sleep 1
seconds=$((seconds + 1))
done
if kill -0 "$pid" 2>/dev/null; then
- echo "wrapper: podman exec still running after ${seconds}s; sending SIGQUIT for goroutine dump" >&2
+ {
+ echo "wrapper: podman exec still running after ${seconds}s; diagnostics:"
+ echo "--- podman-family processes:"
+ pgrep -af 'podman|conmon|pasta|catatonit' || true
+ echo "--- processes with the libpod lock segment mapped:"
+ for p in $(sudo grep -l libpod /proc/[0-9]*/maps 2>/dev/null | cut -d/ -f3); do
+ echo "pid $p: $(tr '\0' ' ' <"/proc/$p/cmdline" 2>/dev/null)"
+ done
+ echo "--- kernel stacks:"
+ for p in $(pgrep 'podman|conmon'); do
+ echo "pid $p: $(tr '\0' ' ' <"/proc/$p/cmdline" 2>/dev/null)"
+ sudo cat "/proc/$p/stack" 2>/dev/null || true
+ done
+ echo "--- goroutine dump of the stuck client ($pid):"
+ } >&2
kill -QUIT "$pid" 2>/dev/null || true
sleep 5
kill -KILL "$pid" 2>/dev/null || true
--
2.51.2
From ab0d501f3b5eb8a1df3c4515dd75471152090ee8 Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Sat, 29 Aug 2026 15:44:37 -0700
Subject: [PATCH 09/10] ci: use file-based libpod locks in the lint job
Diagnostics from the hung run show the stuck podman exec is the ONLY
process with the libpod lock segment mapped: the SHM container-lock
semaphore was leaked by a process that died holding it, and SHM
semaphores never auto-release. Switch libpod to lock_type=file (flock
releases on holder death) before any podman use. Also raise the
wrapper's hang guard back to 900s for cold-cache lint runs.
Co-Authored-By: Claude Fable 5
Claude-Session: https://claude.ai/code/session_01NaDYk2PGWfSLEXeN5CBJFN
---
.github/workflows/golangci-lint.yaml | 6 ++++++
hack/golangci-lint | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml
index af7de5e6..7dfb71fb 100644
--- a/.github/workflows/golangci-lint.yaml
+++ b/.github/workflows/golangci-lint.yaml
@@ -31,6 +31,12 @@ jobs:
- name: run build for linting
run: |
sudo apt install podman -y
+ # libpod's default SHM semaphores leak when a lock holder dies
+ # (observed on runner images >= 20260823: an exec-session cleanup
+ # process dies holding the container lock and every later podman
+ # exec blocks forever in sem_wait); file locks auto-release
+ mkdir -p ~/.config/containers
+ printf '[engine]\nlock_type = "file"\n' >> ~/.config/containers/containers.conf
sudo ln -s $(realpath ./hack/golangci-lint) /usr/bin/golangci-lint
make golangci-lint-container BUILDER_TARGET=builder-no-darwin DOCKER_BUILD_OPTS="--layers --cache-to ghcr.io/streamplace/streamplace --cache-from ghcr.io/streamplace/streamplace"
diff --git a/hack/golangci-lint b/hack/golangci-lint
index f2fad0e9..0884b76f 100755
--- a/hack/golangci-lint
+++ b/hack/golangci-lint
@@ -19,7 +19,7 @@ podman exec golangci-lint golangci-lint "$@" "$OUT" 2>"$ERR" &
pid=$!
seconds=0
-while kill -0 "$pid" 2>/dev/null && [ "$seconds" -lt 300 ]; do
+while kill -0 "$pid" 2>/dev/null && [ "$seconds" -lt 900 ]; do
sleep 1
seconds=$((seconds + 1))
done
--
2.51.2
From 80fbee6853e453f6d47c5140c0ef6c890a108aad Mon Sep 17 00:00:00 2001
From: Eli Mallon
Date: Tue, 1 Sep 2026 01:24:31 -0700
Subject: [PATCH 10/10] v0.11.29
---
js/app/package.json | 2 +-
js/components/package.json | 2 +-
js/docs/package.json | 2 +-
lerna.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/js/app/package.json b/js/app/package.json
index d6a4c713..d631648c 100644
--- a/js/app/package.json
+++ b/js/app/package.json
@@ -1,7 +1,7 @@
{
"name": "@streamplace/app",
"main": "./src/entrypoint.tsx",
- "version": "0.11.28",
+ "version": "0.11.29",
"runtimeVersion": "0.10.0",
"scripts": {
"start": "npx expo start -c --port 38081",
diff --git a/js/components/package.json b/js/components/package.json
index b2c19f3f..b43f6e1c 100644
--- a/js/components/package.json
+++ b/js/components/package.json
@@ -1,6 +1,6 @@
{
"name": "@streamplace/components",
- "version": "0.11.28",
+ "version": "0.11.29",
"description": "Streamplace React (Native) Components",
"main": "dist/index.js",
"types": "src/index.tsx",
diff --git a/js/docs/package.json b/js/docs/package.json
index 6620713d..5d931403 100644
--- a/js/docs/package.json
+++ b/js/docs/package.json
@@ -1,7 +1,7 @@
{
"name": "streamplace-docs",
"type": "module",
- "version": "0.11.28",
+ "version": "0.11.29",
"scripts": {
"dev": "astro dev --host 0.0.0.0 --port 38082",
"start": "astro dev --host 0.0.0.0 --port 38082",
diff --git a/lerna.json b/lerna.json
index 33c62192..8f3ce688 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
- "version": "0.11.28",
+ "version": "0.11.29",
"npmClient": "pnpm"
}