From bdc6231aa365affcd8a41c416a09160677581a19 Mon Sep 17 00:00:00 2001 From: alexbelgium Date: Thu, 16 Jul 2026 10:36:05 +0200 Subject: [PATCH 1/2] fix(claude_desktop): repair Headroom MCP model cache, HOME dedup, gitconfig owner Three add-on runtime-environment bugs, all found while investigating a Headroom dashboard stuck at 0 gain. Headroom MCP server had no HF_HOME. 1.27 fixed the Kompress model cache for the svc-headroom proxy longrun by exporting HF_HOME there, but the MCP server is a different process: Claude Desktop and Claude Code spawn it from the registered mcpServers entry, so it never saw that export and kept resolving the HuggingFace cache to ~/.cache, which this add-on symlinks to tmpfs. Its Kompress ML path therefore never found the model, re-downloaded ~270 MB into tmpfs on every boot, and lost it on the next one -- headroom_compress returned router:noop (output unchanged) for prose and other unstructured content. Rule-based compression (SmartCrusher, structured tool output) was unaffected and worked throughout, which is why the failure only showed on some payloads. Carry env.HF_HOME on the managed headroom entry in both claude_desktop_config.json and ~/.claude.json. ~/.gitconfig was written as root and left unreadable by abc. `git config --global` ran as root during init and rewrites the file on every start, so 20-folders.sh's earlier recursive chown never stuck to it; .config/gh survived abc-owned only because the "already authenticated" branch skips rewriting it. The user that actually runs git, gh and Claude could not read its own committer identity or the gh credential helper: every commit failed with "Author identity unknown" and authenticated pushes fell back to prompting. Run the git/gh setup as abc via s6-setuidgid, matching 81-tokensave_repositories.sh, and reclaim root-owned copies left by earlier versions before writing. ~/.bashrc accumulated stale HOME/FM_HOME exports across data_location changes. The idempotency guard only tested for the current $LOCATION, so changing the option and later changing it back appended a second block while leaving the first, and the last one written won for every interactive shell. $HOME then pointed at a directory the add-on no longer manages, so anything resolving config through it read the wrong path -- `headroom doctor` reported "claude: not routed (no ~/.claude/settings.json)" against a correctly routed install, and bare `headroom` invocations created a stray .headroom tree under the old location. Make the block marker-delimited and rewrite it from scratch each boot. Verified on a running add-on: headroom_compress now reports 1909 -> 1122 tokens (41.2%, router:mixed) through the live MCP server; `headroom doctor` reports "claude: routed via /data/data/.claude/settings.json"; and git commits work as abc without a repo-local identity override. Co-Authored-By: Claude Opus 4.8 --- claude_desktop/CHANGELOG.md | 8 +++- claude_desktop/config.yaml | 2 +- .../rootfs/etc/cont-init.d/20-folders.sh | 15 ++++++- .../rootfs/etc/cont-init.d/82-claude_tools.sh | 6 +++ .../rootfs/etc/cont-init.d/83-github_cli.sh | 39 +++++++++++++++---- 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/claude_desktop/CHANGELOG.md b/claude_desktop/CHANGELOG.md index 2da9444603..00c950b8b6 100644 --- a/claude_desktop/CHANGELOG.md +++ b/claude_desktop/CHANGELOG.md @@ -1,5 +1,9 @@ -## 1.28 (16-07-2026) -- Minor bugs fixed +## 1.29 (16-07-2026) + +- Point the Headroom MCP server at the persistent Kompress model cache. 1.27 set `HF_HOME` on the `svc-headroom` proxy longrun only, but the MCP server is a separate process spawned by Claude Desktop / Claude Code from the registered `mcpServers` entry, so it never inherited that export and kept resolving the HuggingFace cache to `~/.cache` — symlinked to tmpfs here and wiped on every restart. Its Kompress ML path therefore never found the model, re-downloaded ~270 MB into tmpfs on each boot, and lost it again on the next one; `headroom_compress` fell back to `router:noop` (unchanged output) on prose and other unstructured content. The managed `headroom` entry in both `claude_desktop_config.json` and `~/.claude.json` now carries `env.HF_HOME` pointing at the same `~/.headroom/hf` cache the proxy warms. Rule-based compression (SmartCrusher, structured tool output) was unaffected and worked throughout. +- Fix `~/.gitconfig` being written as `root` and left unreadable by the `abc` runtime user, which broke git for the user that actually runs it: every commit failed with `Author identity unknown` and the `gh` credential helper was invisible to authenticated pushes. `git config --global` ran as root during init and rewrites the file on every start, so `20-folders.sh`'s earlier recursive chown never stuck to it (`.config/gh` survived abc-owned only because the "already authenticated" branch skips rewriting it). The git/gh setup now runs as `abc` via `s6-setuidgid`, matching `81-tokensave_repositories.sh`, and reclaims any root-owned copies left by an earlier version before writing. +- Fix `~/.bashrc` accumulating stale `HOME`/`FM_HOME` exports when `data_location` changes. The idempotency guard only tested for the *current* `$LOCATION`, so changing the option and later changing it back appended a second block while leaving the first, and the last one written won for every interactive shell — leaving `$HOME` pointing at a directory the add-on no longer manages. Any tool that resolves config through `$HOME` then read the wrong path (`headroom doctor` reported `claude: not routed (no ~/.claude/settings.json)` against a correctly routed install, and bare `headroom` invocations created a stray `.headroom` tree under the old location). The block is now marker-delimited and rewritten from scratch on every boot, so it is idempotent across any number of `data_location` changes. + ## 1.27 (15-07-2026) - Route Claude Desktop cowork/local-agent-mode sessions through the Headroom proxy. Desktop spawns its bundled Claude Code binary at an absolute path (bypassing the add-on's PATH wrapper) with `ANTHROPIC_BASE_URL` pinned to the production endpoint, so those sessions never produced proxy savings. The add-on now manages `env.ANTHROPIC_BASE_URL` in `~/.claude/settings.json` — settings `env` entries replace inherited environment values at CLI startup — gated on `headroom_wrap_claude_code` and never overwriting a user-customized endpoint. diff --git a/claude_desktop/config.yaml b/claude_desktop/config.yaml index 7cd8b5630a..bbfd56b2da 100644 --- a/claude_desktop/config.yaml +++ b/claude_desktop/config.yaml @@ -109,5 +109,5 @@ slug: claude_desktop tmpfs: true udev: true url: https://github.com/alexbelgium/hassio-addons -version: "1.28" +version: "1.29" video: true diff --git a/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh b/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh index 6ef48206a9..845c9aa3d9 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh @@ -85,10 +85,23 @@ printf "%s" "$LOCATION" > "$S6_ENVDIR/HOME" printf "%s" "$LOCATION" > "$S6_ENVDIR/FM_HOME" printf "%s" "/tmp/cache" > "$S6_ENVDIR/XDG_CACHE_HOME" printf "%s" "$XDG_RUNTIME_DIR" > "$S6_ENVDIR/XDG_RUNTIME_DIR" -grep -qxF "export HOME=\"$LOCATION\"" ~/.bashrc 2>/dev/null || { +# Re-derived on every boot rather than gated on a "does it already say $LOCATION" grep: that +# guard only ever recognized the CURRENT $LOCATION, so a user who changed data_location and +# later changed it back left two stale HOME/FM_HOME exports in ~/.bashrc, with the last one +# (not necessarily the correct one) winning for every interactive shell. The marker makes this +# idempotent regardless of how many times $LOCATION has changed: strip any previously managed +# block, then append one that reflects the current value. +BASHRC_HOME_BEGIN="# --- BEGIN ADDON HOME (managed) ---" +BASHRC_HOME_END="# --- END ADDON HOME (managed) ---" +if [ -f ~/.bashrc ]; then + sed -i "/^${BASHRC_HOME_BEGIN}\$/,/^${BASHRC_HOME_END}\$/d" ~/.bashrc +fi +{ + printf "%s\n" "$BASHRC_HOME_BEGIN" printf "%s\n" "export HOME=\"$LOCATION\"" printf "%s\n" "export FM_HOME=\"$LOCATION\"" printf "%s\n" "export XDG_CACHE_HOME=\"/tmp/cache\"" + printf "%s\n" "$BASHRC_HOME_END" } >> ~/.bashrc bashio::log.info "Creating $LOCATION" 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 8f01b01db2..4cf1a023b2 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 @@ -76,6 +76,7 @@ if bashio::config.true 'enable_ha_mcp'; then fi HEADROOM_ENABLED="$HEADROOM_ENABLED" HEADROOM_BIN="$(command -v headroom || echo headroom)" \ + HEADROOM_HF_HOME="${HOME}/.headroom/hf" \ TOKENSAVE_ENABLED="$TOKENSAVE_ENABLED" TOKENSAVE_BIN="$(command -v tokensave || echo tokensave)" \ HA_MCP_ENABLED="$HA_MCP_ENABLED" HA_MCP_URL="$HA_MCP_URL" HA_MCP_TOKEN="$HA_MCP_TOKEN" \ MCP_PROXY_BIN="$(command -v mcp-proxy || echo mcp-proxy)" \ @@ -96,6 +97,11 @@ if os.environ["HEADROOM_ENABLED"] == "true": desired["headroom"] = { "command": os.environ["HEADROOM_BIN"], "args": ["mcp", "serve", "--proxy-url", "http://127.0.0.1:8787"], + # The MCP server is a separate process from the svc-headroom proxy longrun and does + # not inherit its HF_HOME export, so Kompress falls back to the default (tmpfs, wiped + # every restart) cache dir, never finds the model, and silently no-ops every + # compression request. Point it at the same persistent cache the proxy warms. + "env": {"HF_HOME": os.environ["HEADROOM_HF_HOME"]}, } if os.environ["TOKENSAVE_ENABLED"] == "true": desired["tokensave"] = {"command": os.environ["TOKENSAVE_BIN"], "args": ["serve"]} diff --git a/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh b/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh index a836bef8b1..14b1a47af2 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh @@ -17,25 +17,50 @@ if ! command -v gh > /dev/null 2>&1; then exit 0 fi +# Everything below writes into the abc runtime user's HOME, so it must run AS abc. cont-init +# runs as root with HOME already pointing at the persistent data location, so plain +# `git config --global` recreated ~/.gitconfig owned by root:root on every start — and because +# that file is rewritten each boot, 20-folders.sh's earlier recursive chown never stuck to it. +# The user who actually runs git, gh and Claude was then unable to read its own committer +# identity or the gh credential helper, so every commit failed with "Author identity unknown" +# and authenticated pushes fell back to prompting. 20-folders.sh already remapped abc to the +# effective runtime identity (never root in bypass mode), so follow abc rather than re-reading +# the raw PUID/PGID options here. +RUNTIME_UID="$(id -u abc)" +RUNTIME_GID="$(id -g abc)" + +run_as_runtime_user() { + s6-setuidgid abc env HOME="$HOME" "$@" +} + +# Reclaim any root-owned copies left by an earlier add-on version before writing as abc: +# these paths are not covered by 82-claude_tools.sh's ownership pass, and a root-owned +# ~/.gitconfig would make the first `git config` below fail outright under `set -e`. +for managed_path in "$HOME/.gitconfig" "$HOME/.config/gh"; do + if [ -e "$managed_path" ]; then + chown -R -- "${RUNTIME_UID}:${RUNTIME_GID}" "$managed_path" || bashio::log.warning "Unable to set ownership on $managed_path" + fi +done + if bashio::config.has_value 'github_username'; then - git config --global user.name "$(bashio::config 'github_username')" + run_as_runtime_user git config --global user.name "$(bashio::config 'github_username')" fi if bashio::config.has_value 'github_email'; then - git config --global user.email "$(bashio::config 'github_email')" + run_as_runtime_user git config --global user.email "$(bashio::config 'github_email')" fi if bashio::config.has_value 'github_token'; then token="$(bashio::config 'github_token')" - mkdir -p "$HOME/.config/gh" - chmod 700 "$HOME/.config/gh" - if env -u GH_TOKEN -u GITHUB_TOKEN gh auth status --hostname github.com > /dev/null 2>&1; then + run_as_runtime_user mkdir -p "$HOME/.config/gh" + run_as_runtime_user chmod 700 "$HOME/.config/gh" + if run_as_runtime_user env -u GH_TOKEN -u GITHUB_TOKEN gh auth status --hostname github.com > /dev/null 2>&1; then bashio::log.info "GitHub CLI already authenticated for github.com" else bashio::log.info "Configuring GitHub CLI authentication for github.com" - printf '%s\n' "$token" | env -u GH_TOKEN -u GITHUB_TOKEN gh auth login --hostname github.com --with-token || bashio::log.warning "GitHub CLI authentication failed" + printf '%s\n' "$token" | run_as_runtime_user env -u GH_TOKEN -u GITHUB_TOKEN gh auth login --hostname github.com --with-token || bashio::log.warning "GitHub CLI authentication failed" fi - env -u GH_TOKEN -u GITHUB_TOKEN gh auth setup-git --hostname github.com || bashio::log.warning "GitHub CLI git credential setup failed" + run_as_runtime_user env -u GH_TOKEN -u GITHUB_TOKEN gh auth setup-git --hostname github.com || bashio::log.warning "GitHub CLI git credential setup failed" else bashio::log.info "GitHub CLI available. Set github_token to authenticate gh and git operations." fi From 95641d253cf2310d0e4701905e6a92bbc0d050f1 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:49:02 +0200 Subject: [PATCH 2/2] Update 83-github_cli.sh --- claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh b/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh index 14b1a47af2..3df1f2f9ee 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/83-github_cli.sh @@ -36,6 +36,10 @@ run_as_runtime_user() { # Reclaim any root-owned copies left by an earlier add-on version before writing as abc: # these paths are not covered by 82-claude_tools.sh's ownership pass, and a root-owned # ~/.gitconfig would make the first `git config` below fail outright under `set -e`. + +mkdir -p "$HOME/.config" +chown -- "${RUNTIME_UID}:${RUNTIME_GID}" "$HOME/.config" + for managed_path in "$HOME/.gitconfig" "$HOME/.config/gh"; do if [ -e "$managed_path" ]; then chown -R -- "${RUNTIME_UID}:${RUNTIME_GID}" "$managed_path" || bashio::log.warning "Unable to set ownership on $managed_path"