mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-09-01 17:53:32 +02:00
Merge pull request #2873 from alexbelgium/claude/claude-desktop-startup-hang-9t4brj
fix(claude_desktop): fix "waiting for stream" hang — GPU race + pin selkies base image
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
## 1.31 (16-07-2026)
|
||||
|
||||
- Pin the LinuxServer selkies base image to a fixed version (`…-debianbookworm-45960cc3-ls113`) instead of the rolling `…-debianbookworm` tag. The rolling tag is rebuilt continuously (and itself installs selkies "latest" at base-build time), so the desktop/stream runtime could change under the add-on with no change to its own files — builds are now reproducible and the base only moves when this value is bumped deliberately. The pinned tags resolve to exactly the image the rolling tag currently points at (amd64 `sha256:6a4d5154…`, aarch64 `sha256:90914dfd…`).
|
||||
- Fix Claude Desktop never appearing — the Selkies web client stayed on "waiting for stream" forever with `libEGL warning: failed to open /dev/dri/card0: Permission denied` in the log. The LinuxServer base image grants the desktop user (`abc`) access to the `/dev/dri` render nodes in its `init-video` s6 oneshot, but that oneshot is not a dependency of `svc-xorg`/`svc-selkies`/`svc-de`, so on Home Assistant those long-running services regularly start (via `s6-setuidgid abc`) *before* `abc` has been added to the render group. Xorg/Selkies/pixelflux then open the render device without permission, the video pipeline produces no frames, and the stream never starts. Prepare the exposed DRI nodes in a new `21-gpu_permissions.sh` cont-init script instead: `cont-init.d` runs to completion before any s6-rc service starts, so `abc` is added to each node's owning group (and the node is made world read/write as a timing-independent fallback) in time for the graphical services to use the GPU. Best-effort and a no-op on hosts that expose no GPU.
|
||||
|
||||
## 1.30 (16-07-2026)
|
||||
|
||||
- Compress large tool outputs automatically in every Claude Code session with a managed `PostToolUse` hook (new `headroom_auto_compress` option, enabled by default). Desktop-spawned sessions (cowork/dispatch) pin `ANTHROPIC_BASE_URL` to the production endpoint (headroom #869), so the transparent proxy never sees their traffic and compression there depended entirely on the model remembering to call the `headroom` MCP tools per the CLAUDE.md guidance — in practice most large outputs went uncompressed. The new `/usr/local/bin/headroom-posttooluse-compress.py` hook fires on `Bash`/`Grep`/`Glob`/`WebFetch` results over ~4000 characters, compresses them with Headroom's rule-based pipeline (SmartCrusher and friends; the Kompress ML path is disabled because its background model load can never complete inside a short-lived hook process), and swaps the result in via `hookSpecificOutput.updatedToolOutput` with a retrieval marker appended. Originals are stored in the shared CCR SQLite store (`~/.headroom/ccr_store.db` — the same one the headroom MCP server reads), so `mcp__headroom__headroom_retrieve` always recovers the full output; savings are recorded to the durable ledger (client `posttooluse-hook`) and show up in the existing gains report. The hook fails open (any error leaves the tool output untouched), never touches `stderr` fields so error text reaches the model verbatim, skips anything below a 50-token savings floor, and is registered idempotently in `~/.claude/settings.json` only after a `--self-test` confirms the interpreter can import headroom; disabling the option (or Headroom) removes the managed entry without touching user-defined hooks. Measured on a representative Home Assistant `states` dump: 10781 -> 2964 tokens (73% saved) at ~1.7 s hook overhead, with sub-100 ms pass-through for small outputs.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/linuxserver/baseimage-selkies:arm64v8-debianbookworm",
|
||||
"amd64": "ghcr.io/linuxserver/baseimage-selkies:amd64-debianbookworm"
|
||||
"aarch64": "ghcr.io/linuxserver/baseimage-selkies:arm64v8-debianbookworm-45960cc3-ls113",
|
||||
"amd64": "ghcr.io/linuxserver/baseimage-selkies:amd64-debianbookworm-45960cc3-ls113"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,5 +111,5 @@ slug: claude_desktop
|
||||
tmpfs: true
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "1.30"
|
||||
version: "1.31"
|
||||
video: true
|
||||
|
||||
52
claude_desktop/rootfs/etc/cont-init.d/21-gpu_permissions.sh
Executable file
52
claude_desktop/rootfs/etc/cont-init.d/21-gpu_permissions.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
# Grant the shared desktop user (abc) access to the exposed GPU render nodes *before* the
|
||||
# graphical services start.
|
||||
#
|
||||
# The LinuxServer base image already sets up /dev/dri group access, but it does so in its
|
||||
# init-video s6 oneshot, which is NOT a dependency of svc-xorg/svc-selkies/svc-de. On Home
|
||||
# Assistant those long-running services routinely start (as abc, via s6-setuidgid) before
|
||||
# init-video has added abc to the render group, so Xorg/Selkies/pixelflux open the render
|
||||
# device with the wrong credentials and fail:
|
||||
#
|
||||
# libEGL warning: failed to open /dev/dri/card0: Permission denied
|
||||
#
|
||||
# With no usable render node the video pipeline never produces frames, so the Selkies web
|
||||
# client stays on "waiting for stream" indefinitely and Claude Desktop never appears.
|
||||
#
|
||||
# cont-init.d runs to completion before any s6-rc service is started, so preparing the DRI
|
||||
# nodes here wins that race. Everything is best-effort: a host that exposes no GPU simply has
|
||||
# no nodes to touch and this is a no-op.
|
||||
|
||||
shopt -s nullglob
|
||||
dri_nodes=(/dev/dri/card* /dev/dri/render*)
|
||||
if [ "${#dri_nodes[@]}" -eq 0 ]; then
|
||||
bashio::log.info "No /dev/dri render nodes exposed; skipping GPU permission setup"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for node in "${dri_nodes[@]}"; do
|
||||
[ -e "$node" ] || continue
|
||||
|
||||
# Mirror the base image's init-video logic (add abc to the node's owning group, creating
|
||||
# the group when the GID is unnamed) but early enough that the s6-setuidgid at service
|
||||
# start picks the membership up.
|
||||
gid="$(stat -c '%g' "$node")"
|
||||
gname="$(getent group "$gid" | awk -F: '{print $1}')"
|
||||
if [ -z "$gname" ]; then
|
||||
gname="dri${gid}"
|
||||
groupadd -o -g "$gid" "$gname" 2> /dev/null || true
|
||||
fi
|
||||
if ! id -G abc 2> /dev/null | tr ' ' '\n' | grep -qx "$gid"; then
|
||||
usermod -a -G "$gname" abc 2> /dev/null || true
|
||||
fi
|
||||
|
||||
# Guarantee access even where group propagation is unreliable inside the add-on sandbox:
|
||||
# this is a single-user desktop container, so world read/write on the local render node
|
||||
# is acceptable and removes any dependency on group-membership timing.
|
||||
chmod o+rw "$node" 2> /dev/null || true
|
||||
|
||||
bashio::log.info "GPU: prepared ${node} (group ${gname}:${gid}) for the desktop user"
|
||||
done
|
||||
Reference in New Issue
Block a user