From 74e25ba41b74512c33b3871bf01551fc48c3ef53 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 13:18:11 +0000 Subject: [PATCH 1/4] claude_desktop: don't wrap desktop launch with invalid headroom command install_headroom defaults to true and headroom is baked into the image, so 82-claude_tools.sh rewrote the desktop launch to "headroom wrap claude-desktop --no-sandbox ...". headroom "wrap" only accepts coding-agent CLIs (claude, codex, cursor, ...) and expects agent arguments after a "--" separator, so "claude-desktop" plus the Electron flags is an invalid wrap target/options. The autostart ran only that command with no fallback, leaving the desktop app unlaunched for default users. - Keep the plain claude-desktop launch; just expose headroom with a usage hint (matches the documented "make headroom available and log a usage hint"). - Harden autostart to fall back to the default launch if a custom/wrapped command fails to start. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw --- claude_desktop/CHANGELOG.md | 5 +++++ claude_desktop/config.yaml | 2 +- claude_desktop/rootfs/defaults/autostart | 14 ++++++++++++-- .../rootfs/etc/cont-init.d/82-claude_tools.sh | 7 +++++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/claude_desktop/CHANGELOG.md b/claude_desktop/CHANGELOG.md index 3a36c5e444..532e04f811 100644 --- a/claude_desktop/CHANGELOG.md +++ b/claude_desktop/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.6 (07-07-2026) + +- Fix default desktop launch failing when `install_headroom` is enabled (the default). `headroom wrap` only supports coding-agent CLIs (`claude`, `codex`, ...) with agent arguments after `--`, so wrapping `claude-desktop` produced an invalid command that left the app unlaunched. Keep the plain Claude Desktop launch and instead just expose `headroom` with a usage hint. +- Make the autostart resilient: if a custom/wrapped launch command fails to start, fall back to the default Claude Desktop launch so the app still comes up. + ## 1.5 (07-07-2026) - Change the default Claude Desktop data location to `/data/data`. diff --git a/claude_desktop/config.yaml b/claude_desktop/config.yaml index 8be3ea09f0..40520d3614 100644 --- a/claude_desktop/config.yaml +++ b/claude_desktop/config.yaml @@ -90,5 +90,5 @@ slug: claude_desktop tmpfs: true udev: true url: https://github.com/alexbelgium/hassio-addons -version: "1.5" +version: "1.6" video: true diff --git a/claude_desktop/rootfs/defaults/autostart b/claude_desktop/rootfs/defaults/autostart index fe730c47cb..99e73bf659 100644 --- a/claude_desktop/rootfs/defaults/autostart +++ b/claude_desktop/rootfs/defaults/autostart @@ -14,8 +14,18 @@ dbus-update-activation-environment --all >/dev/null 2>&1 || true # --password-store=gnome-libsecret forces Electron onto the libsecret backend instead of # falling back to plaintext (and warning that the sign-in will not be saved). CLAUDE_DESKTOP_COMMAND_FILE="/tmp/claude-desktop-command" +DEFAULT_CLAUDE_DESKTOP_COMMAND="claude-desktop --no-sandbox --disable-dev-shm-usage --password-store=gnome-libsecret" if [ -s "$CLAUDE_DESKTOP_COMMAND_FILE" ]; then - sh -c "$(cat "$CLAUDE_DESKTOP_COMMAND_FILE")" + CLAUDE_DESKTOP_COMMAND="$(cat "$CLAUDE_DESKTOP_COMMAND_FILE")" else - claude-desktop --no-sandbox --disable-dev-shm-usage --password-store=gnome-libsecret + CLAUDE_DESKTOP_COMMAND="$DEFAULT_CLAUDE_DESKTOP_COMMAND" +fi + +# Launch the configured command. If a custom/wrapped command fails to start, fall back to +# the plain Claude Desktop launch so the desktop app still comes up for the user. +if ! sh -c "$CLAUDE_DESKTOP_COMMAND"; then + if [ "$CLAUDE_DESKTOP_COMMAND" != "$DEFAULT_CLAUDE_DESKTOP_COMMAND" ]; then + echo "autostart: '$CLAUDE_DESKTOP_COMMAND' failed; falling back to default Claude Desktop launch" >&2 + exec sh -c "$DEFAULT_CLAUDE_DESKTOP_COMMAND" + fi fi diff --git a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh index b160340676..6a048783c6 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh @@ -10,8 +10,11 @@ printf '%s\n' "$DEFAULT_CLAUDE_DESKTOP_COMMAND" > "$CLAUDE_DESKTOP_COMMAND_FILE" if bashio::config.true 'install_headroom'; then if command -v headroom &> /dev/null; then - bashio::log.info "headroom $(headroom --version 2> /dev/null || true) available; launching Claude Desktop through headroom" - printf '%s\n' "headroom wrap $DEFAULT_CLAUDE_DESKTOP_COMMAND" > "$CLAUDE_DESKTOP_COMMAND_FILE" + # headroom "wrap" only supports coding-agent CLIs (claude|codex|cursor|...), + # not the Claude Desktop Electron app. Wrapping "claude-desktop" produced an + # invalid command that left the desktop app unlaunched, so keep the default + # launch untouched and just expose headroom for the user to wrap Claude Code. + bashio::log.info "headroom $(headroom --version 2> /dev/null || true) available; run 'headroom wrap claude' in a terminal to route Claude Code through headroom" else bashio::log.warning "headroom is not available" fi From f65d5af969d9ecf8ac8cdbb9bd1cba0e5a76c99a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 14:05:53 +0000 Subject: [PATCH 2/4] claude_desktop: launch through headroom proxy instead of invalid wrap headroom "wrap" only accepts coding-agent CLIs (claude, codex, cursor, ...) with agent args after "--", so it cannot wrap the claude-desktop Electron app. Route the launch through headroom's standalone compression proxy instead: the command file now starts "headroom proxy" and points Claude Desktop at it via ANTHROPIC_BASE_URL, while the autostart still falls back to a plain launch if that fails. Claude Desktop currently force-overrides ANTHROPIC_BASE_URL (headroom #869), so transparent compression only engages once upstream adds Desktop support; until then the app launches normally. Update the README/CHANGELOG accordingly. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw --- claude_desktop/CHANGELOG.md | 4 ++-- claude_desktop/README.md | 2 +- .../rootfs/etc/cont-init.d/82-claude_tools.sh | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/claude_desktop/CHANGELOG.md b/claude_desktop/CHANGELOG.md index 532e04f811..3a50ec848b 100644 --- a/claude_desktop/CHANGELOG.md +++ b/claude_desktop/CHANGELOG.md @@ -1,7 +1,7 @@ ## 1.6 (07-07-2026) -- Fix default desktop launch failing when `install_headroom` is enabled (the default). `headroom wrap` only supports coding-agent CLIs (`claude`, `codex`, ...) with agent arguments after `--`, so wrapping `claude-desktop` produced an invalid command that left the app unlaunched. Keep the plain Claude Desktop launch and instead just expose `headroom` with a usage hint. -- Make the autostart resilient: if a custom/wrapped launch command fails to start, fall back to the default Claude Desktop launch so the app still comes up. +- Fix default desktop launch failing when `install_headroom` is enabled (the default): `headroom wrap` only supports coding-agent CLIs (`claude`, `codex`, ...) with agent arguments after `--`, so wrapping `claude-desktop` produced an invalid command that left the app unlaunched. Launch Claude Desktop through headroom's standalone compression proxy (`headroom proxy` + `ANTHROPIC_BASE_URL`) instead. Note: Claude Desktop currently overrides `ANTHROPIC_BASE_URL` ([headroom #869](https://github.com/headroomlabs-ai/headroom/issues/869)), so transparent compression engages only once upstream adds Desktop support; the app launches normally in the meantime. +- Make the autostart resilient: if the headroom/custom launch command fails to start, fall back to the plain Claude Desktop launch so the app always comes up. ## 1.5 (07-07-2026) diff --git a/claude_desktop/README.md b/claude_desktop/README.md index 5cb6e4421d..76afc1e432 100644 --- a/claude_desktop/README.md +++ b/claude_desktop/README.md @@ -38,7 +38,7 @@ Claude Desktop sign-in requires a claude.ai plan that supports the Desktop app. | `DRINODE` | | Optional GPU device override for Selkies. | | `DNS_server` | `8.8.8.8` | DNS server used by the standard DNS module. | | `auto_update` | `true` | Check Anthropic's apt repository and upgrade `claude-desktop` at add-on startup. | -| `install_headroom` | `true` | Make the baked-in `headroom` command available and log a usage hint. | +| `install_headroom` | `true` | Launch Claude Desktop through the baked-in `headroom` compression proxy. Note: Claude Desktop currently overrides the proxy endpoint ([headroom #869](https://github.com/headroomlabs-ai/headroom/issues/869)), so transparent compression only engages once upstream adds Desktop support; the app still launches normally, and falls back to a plain launch if the proxy command fails. | | `install_rtk` | `true` | Configure the rtk Claude Code `PreToolUse` hook in the persistent Claude Code settings. | | `install_caveman` | `true` | Install the caveman Claude Code plugin into the persistent Claude Code home. | | `install_github_cli` | `true` | Enable first-start checks and setup for the baked-in `git` and `gh` commands. | diff --git a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh index 6a048783c6..ed081a882c 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh @@ -10,11 +10,18 @@ printf '%s\n' "$DEFAULT_CLAUDE_DESKTOP_COMMAND" > "$CLAUDE_DESKTOP_COMMAND_FILE" if bashio::config.true 'install_headroom'; then if command -v headroom &> /dev/null; then - # headroom "wrap" only supports coding-agent CLIs (claude|codex|cursor|...), - # not the Claude Desktop Electron app. Wrapping "claude-desktop" produced an - # invalid command that left the desktop app unlaunched, so keep the default - # launch untouched and just expose headroom for the user to wrap Claude Code. - bashio::log.info "headroom $(headroom --version 2> /dev/null || true) available; run 'headroom wrap claude' in a terminal to route Claude Code through headroom" + # headroom "wrap" only supports coding-agent CLIs (claude|codex|cursor|...) and is + # invalid for the Claude Desktop Electron app, so route the launch through headroom's + # standalone compression proxy instead: start "headroom proxy" and point Claude Desktop + # at it via ANTHROPIC_BASE_URL. + # + # NOTE: Claude Desktop currently force-overrides ANTHROPIC_BASE_URL to the production + # endpoint (headroom #869), so compression only actually engages once upstream adds + # Desktop support. Until then the app still launches normally, and the autostart falls + # back to a plain launch if this command fails. + HEADROOM_PROXY_PORT="8787" + bashio::log.info "headroom $(headroom --version 2> /dev/null || true) available; launching Claude Desktop through the headroom proxy on 127.0.0.1:${HEADROOM_PROXY_PORT} (transparent compression pending Claude Desktop support, headroom #869)" + printf '%s\n' "headroom proxy --port ${HEADROOM_PROXY_PORT} > /tmp/headroom-proxy.log 2>&1 & ANTHROPIC_BASE_URL=http://127.0.0.1:${HEADROOM_PROXY_PORT} ${DEFAULT_CLAUDE_DESKTOP_COMMAND}" > "$CLAUDE_DESKTOP_COMMAND_FILE" else bashio::log.warning "headroom is not available" fi From a9cf84077b78f4b95b0b7090dcbbf4bcd6677cc2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 14:24:38 +0000 Subject: [PATCH 3/4] claude_desktop: register headroom MCP server instead of proxy launch headroom's proxy/wrap routing relies on ANTHROPIC_BASE_URL, which Claude Desktop force-overrides (headroom #869), so it cannot transparently compress the desktop app. Register headroom's MCP server (headroom mcp serve) in Claude Desktop's claude_desktop_config.json instead -- the supported integration -- exposing the headroom_compress/headroom_retrieve/headroom_stats tools inside the app. The plain desktop launch is left untouched. The JSON merge is idempotent, preserves any other MCP servers and top-level keys, backs up malformed config, and removes the entry again when install_headroom is disabled. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw --- claude_desktop/CHANGELOG.md | 4 +- claude_desktop/README.md | 2 +- .../rootfs/etc/cont-init.d/82-claude_tools.sh | 61 +++++++++++++++---- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/claude_desktop/CHANGELOG.md b/claude_desktop/CHANGELOG.md index 3a50ec848b..1cbc84e228 100644 --- a/claude_desktop/CHANGELOG.md +++ b/claude_desktop/CHANGELOG.md @@ -1,7 +1,7 @@ ## 1.6 (07-07-2026) -- Fix default desktop launch failing when `install_headroom` is enabled (the default): `headroom wrap` only supports coding-agent CLIs (`claude`, `codex`, ...) with agent arguments after `--`, so wrapping `claude-desktop` produced an invalid command that left the app unlaunched. Launch Claude Desktop through headroom's standalone compression proxy (`headroom proxy` + `ANTHROPIC_BASE_URL`) instead. Note: Claude Desktop currently overrides `ANTHROPIC_BASE_URL` ([headroom #869](https://github.com/headroomlabs-ai/headroom/issues/869)), so transparent compression engages only once upstream adds Desktop support; the app launches normally in the meantime. -- Make the autostart resilient: if the headroom/custom launch command fails to start, fall back to the plain Claude Desktop launch so the app always comes up. +- Fix default desktop launch failing when `install_headroom` is enabled (the default): the code rewrote the launch to `headroom wrap claude-desktop …`, but `headroom wrap` only supports coding-agent CLIs (`claude`, `codex`, ...) with arguments after `--`, so it produced an invalid command that left the app unlaunched. Leave the plain launch intact and instead register the `headroom` MCP server (`headroom mcp serve`) in Claude Desktop's config, exposing the `headroom_compress`/`headroom_retrieve`/`headroom_stats` tools inside the app. This is the supported headroom integration for Desktop, which overrides `ANTHROPIC_BASE_URL` so transparent proxy compression is not possible ([headroom #869](https://github.com/headroomlabs-ai/headroom/issues/869)). Disabling the option removes the entry again. +- Make the autostart resilient: if the launch command fails to start, fall back to the plain Claude Desktop launch so the app always comes up. ## 1.5 (07-07-2026) diff --git a/claude_desktop/README.md b/claude_desktop/README.md index 76afc1e432..597d9ea9bb 100644 --- a/claude_desktop/README.md +++ b/claude_desktop/README.md @@ -38,7 +38,7 @@ Claude Desktop sign-in requires a claude.ai plan that supports the Desktop app. | `DRINODE` | | Optional GPU device override for Selkies. | | `DNS_server` | `8.8.8.8` | DNS server used by the standard DNS module. | | `auto_update` | `true` | Check Anthropic's apt repository and upgrade `claude-desktop` at add-on startup. | -| `install_headroom` | `true` | Launch Claude Desktop through the baked-in `headroom` compression proxy. Note: Claude Desktop currently overrides the proxy endpoint ([headroom #869](https://github.com/headroomlabs-ai/headroom/issues/869)), so transparent compression only engages once upstream adds Desktop support; the app still launches normally, and falls back to a plain launch if the proxy command fails. | +| `install_headroom` | `true` | Register the baked-in `headroom` MCP server in Claude Desktop, exposing its `headroom_compress`/`headroom_retrieve`/`headroom_stats` context-compression tools inside the app. (Claude Desktop overrides `ANTHROPIC_BASE_URL`, so transparent proxy compression is not possible — MCP is the supported path; see [headroom #869](https://github.com/headroomlabs-ai/headroom/issues/869).) Disabling removes the entry again. | | `install_rtk` | `true` | Configure the rtk Claude Code `PreToolUse` hook in the persistent Claude Code settings. | | `install_caveman` | `true` | Install the caveman Claude Code plugin into the persistent Claude Code home. | | `install_github_cli` | `true` | Enable first-start checks and setup for the baked-in `git` and `gh` commands. | diff --git a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh index ed081a882c..a85da0b9c1 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh @@ -8,23 +8,60 @@ CLAUDE_DESKTOP_COMMAND_FILE="/tmp/claude-desktop-command" DEFAULT_CLAUDE_DESKTOP_COMMAND='claude-desktop --no-sandbox --disable-dev-shm-usage --password-store=gnome-libsecret' printf '%s\n' "$DEFAULT_CLAUDE_DESKTOP_COMMAND" > "$CLAUDE_DESKTOP_COMMAND_FILE" +# headroom's "wrap"/proxy routing works by setting ANTHROPIC_BASE_URL, which the Claude Desktop +# Electron app force-overrides to the production endpoint (headroom #869), so transparent +# compression cannot be applied to the desktop launch. The integration that does work with +# Claude Desktop is headroom's MCP server, which exposes the headroom_compress/headroom_retrieve/ +# headroom_stats tools inside the app. Register it in Claude Desktop's MCP config, leaving the +# plain launch untouched. The merge is idempotent and preserves any other MCP servers. +CLAUDE_DESKTOP_CONFIG="$HOME/.config/Claude/claude_desktop_config.json" if bashio::config.true 'install_headroom'; then if command -v headroom &> /dev/null; then - # headroom "wrap" only supports coding-agent CLIs (claude|codex|cursor|...) and is - # invalid for the Claude Desktop Electron app, so route the launch through headroom's - # standalone compression proxy instead: start "headroom proxy" and point Claude Desktop - # at it via ANTHROPIC_BASE_URL. - # - # NOTE: Claude Desktop currently force-overrides ANTHROPIC_BASE_URL to the production - # endpoint (headroom #869), so compression only actually engages once upstream adds - # Desktop support. Until then the app still launches normally, and the autostart falls - # back to a plain launch if this command fails. - HEADROOM_PROXY_PORT="8787" - bashio::log.info "headroom $(headroom --version 2> /dev/null || true) available; launching Claude Desktop through the headroom proxy on 127.0.0.1:${HEADROOM_PROXY_PORT} (transparent compression pending Claude Desktop support, headroom #869)" - printf '%s\n' "headroom proxy --port ${HEADROOM_PROXY_PORT} > /tmp/headroom-proxy.log 2>&1 & ANTHROPIC_BASE_URL=http://127.0.0.1:${HEADROOM_PROXY_PORT} ${DEFAULT_CLAUDE_DESKTOP_COMMAND}" > "$CLAUDE_DESKTOP_COMMAND_FILE" + bashio::log.info "headroom $(headroom --version 2> /dev/null || true) available; registering the headroom MCP server for Claude Desktop" + HEADROOM_BIN="$(command -v headroom)" CLAUDE_DESKTOP_CONFIG="$CLAUDE_DESKTOP_CONFIG" python3 - <<'PY' || bashio::log.warning "Unable to register the headroom MCP server automatically" +import json +import os +from pathlib import Path + +path = Path(os.environ["CLAUDE_DESKTOP_CONFIG"]) +try: + data = json.loads(path.read_text()) if path.exists() else {} + if not isinstance(data, dict): + data = {} +except Exception: + if path.exists(): + path.rename(path.with_suffix(path.suffix + ".bak")) + data = {} +servers = data.get("mcpServers") +if not isinstance(servers, dict): + servers = {} + data["mcpServers"] = servers +servers["headroom"] = {"command": os.environ.get("HEADROOM_BIN", "headroom"), "args": ["mcp", "serve"]} +path.parent.mkdir(parents=True, exist_ok=True) +path.write_text(json.dumps(data, indent=2) + "\n") +PY else bashio::log.warning "headroom is not available" fi +elif [ -f "$CLAUDE_DESKTOP_CONFIG" ]; then + bashio::log.info "Removing the headroom MCP server from Claude Desktop" + CLAUDE_DESKTOP_CONFIG="$CLAUDE_DESKTOP_CONFIG" python3 - <<'PY' || bashio::log.warning "Unable to remove the headroom MCP server automatically" +import json +import os +from pathlib import Path + +path = Path(os.environ["CLAUDE_DESKTOP_CONFIG"]) +try: + data = json.loads(path.read_text()) +except Exception: + data = None +if isinstance(data, dict): + servers = data.get("mcpServers") + if isinstance(servers, dict) and servers.pop("headroom", None) is not None: + if not servers: + data.pop("mcpServers", None) + path.write_text(json.dumps(data, indent=2) + "\n") +PY fi if bashio::config.true 'install_rtk'; then From 86395a85a044fb06c25067ce700d902780e6287b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 14:35:52 +0000 Subject: [PATCH 4/4] claude_desktop: surface headroom MCP removal failures instead of swallowing them The removal block's json.loads() was wrapped in a bare try/except that set data = None on any parse error, then silently no-op'd (isinstance(data, dict) is False) with exit 0 -- so a malformed claude_desktop_config.json meant the headroom entry was never removed and bashio::log.warning never fired. Drop the try/except so parse errors propagate naturally and the script exits non-zero, matching the sibling rtk removal block's existing convention and triggering the bashio::log.warning fallback. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01MgMcG1QjsSHcDQBxgPpRUw --- claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh index a85da0b9c1..ca1ac2faa5 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/82-claude_tools.sh @@ -51,10 +51,7 @@ import os from pathlib import Path path = Path(os.environ["CLAUDE_DESKTOP_CONFIG"]) -try: - data = json.loads(path.read_text()) -except Exception: - data = None +data = json.loads(path.read_text()) if isinstance(data, dict): servers = data.get("mcpServers") if isinstance(servers, dict) and servers.pop("headroom", None) is not None: