From b2ee4ceb3b38a87f16b90095ecd1edb04df2052f Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Sun, 1 Nov 2026 15:39:25 +0100 Subject: [PATCH] feat(claude_desktop): add opt-in cpu_limit and memory_limit options (#3180) Supervisor exposes no CPU or memory limit for add-ons and `docker update` is lost when the container is recreated, so apply the limits to a child cgroup at every start. Co-authored-by: Claude Opus 5.5 --- claude_desktop/CHANGELOG.md | 3 + claude_desktop/README.md | 10 ++++ claude_desktop/config.yaml | 4 +- .../etc/cont-init.d/19-resource_limits.sh | 59 +++++++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100755 claude_desktop/rootfs/etc/cont-init.d/19-resource_limits.sh diff --git a/claude_desktop/CHANGELOG.md b/claude_desktop/CHANGELOG.md index dc3b260511..3d7a061064 100644 --- a/claude_desktop/CHANGELOG.md +++ b/claude_desktop/CHANGELOG.md @@ -1,4 +1,7 @@ +## 07308545.23 (2026-11-01) +- Add the optional `cpu_limit` (cores) and `memory_limit` (for example `6G`) options, which cap the whole add-on and persist across restarts. Unset keeps the current unlimited behaviour + ## 07308545.22 (2026-10-31) - Update to latest version from aaddrick/claude-desktop-debian (changelog : https://github.com/aaddrick/claude-desktop-debian/releases) - Upstream tag : v3.3.14+claude4.29.0 diff --git a/claude_desktop/README.md b/claude_desktop/README.md index aaa6c27d05..172b4be908 100644 --- a/claude_desktop/README.md +++ b/claude_desktop/README.md @@ -125,9 +125,19 @@ Git synchronization hooks. A repository is indexed only when it is listed in | `enable_ha_api_helper` | `true` | Ship the `ha-cli` Core-API helper and add guidance so Claude can configure Home Assistant without a `/config` mount. | | `additional_apps` | | Comma-separated Debian apt packages to install at startup. | | `additional_pip` | | Comma-separated pip packages installed at startup (via `uv`). | +| `cpu_limit` | | Optional cap on the whole add-on, in CPU cores (for example `2` or `1.5`). Unset means unlimited. See [Limiting CPU and memory](#limiting-cpu-and-memory). | +| `memory_limit` | | Optional memory cap on the whole add-on, for example `6G` or `4096M`. Unset means unlimited. | | `data_location` | `/data/data` | Persistent home directory for Claude and tooling. | | `env_vars` | `[]` | Additional environment variables exported inside the container. | +### Limiting CPU and memory + +Home Assistant has no CPU or memory setting for add-ons, and a manual `docker update` is lost when the add-on restarts. `cpu_limit` and `memory_limit` are applied at every start instead, to everything in the add-on: the desktop, the web interface and all Claude Code sessions with their builds and tests. Changing them requires an add-on restart. + +- `cpu_limit` throttles without killing anything, so a low value also slows the web interface. +- `memory_limit` caps RAM, and allows the same amount again in swap (as `docker --memory` does). Beyond that, the kernel kills a process in the add-on, usually the largest, which can be a running session. +- Both need cgroup v2, which Home Assistant OS uses. If it is not available, the add-on logs a warning and starts without limits. + ### Permission modes ```yaml diff --git a/claude_desktop/config.yaml b/claude_desktop/config.yaml index 7b0db61866..8dd44c719c 100644 --- a/claude_desktop/config.yaml +++ b/claude_desktop/config.yaml @@ -145,7 +145,9 @@ schema: cifsdomain: str? cifspassword: str? cifsusername: str? + cpu_limit: float(0.1,)? localdisks: str? + memory_limit: match(^[1-9][0-9]*[MmGg]$)? networkdisks: str? github_email: str? enable_ha_mcp: bool? @@ -175,5 +177,5 @@ schema: slug: claude_desktop udev: true url: https://github.com/alexbelgium/hassio-addons -version: "07308545.22" +version: "07308545.23" video: true diff --git a/claude_desktop/rootfs/etc/cont-init.d/19-resource_limits.sh b/claude_desktop/rootfs/etc/cont-init.d/19-resource_limits.sh new file mode 100755 index 0000000000..5feeb0a96e --- /dev/null +++ b/claude_desktop/rootfs/etc/cont-init.d/19-resource_limits.sh @@ -0,0 +1,59 @@ +#!/usr/bin/with-contenv bashio +# shellcheck shell=bash +set -e + +# Apply the optional cpu_limit / memory_limit options. +# +# Supervisor has no CPU or memory key for add-ons, and `docker update` is lost when it recreates +# the container, so the limits are set on a child cgroup at every start. nsdelegate forbids +# writing cpu.max / memory.max on the container's own cgroup, and a cgroup can only delegate +# controllers once it holds no process, hence moving everything to the child first. This runs +# early because memory already charged stays with the parent. + +cpu_limit="" +memory_limit="" +if bashio::config.has_value 'cpu_limit'; then + cpu_limit="$(bashio::config 'cpu_limit')" +fi +if bashio::config.has_value 'memory_limit'; then + memory_limit="$(bashio::config 'memory_limit')" +fi +if [[ -z "$cpu_limit" && -z "$memory_limit" ]]; then + exit 0 +fi + +cgroup="$(mktemp -d)" +if ! mount -t cgroup2 none "$cgroup"; then + bashio::log.warning "Could not mount cgroup2: cpu_limit and memory_limit are not applied" + rmdir "$cgroup" + exit 0 +fi + +mkdir -p "$cgroup/addon" +delegated=false +# Retry because processes forked during the move land in the parent and make the write fail +for _ in 1 2 3 4 5; do + xargs -rn1 < "$cgroup/cgroup.procs" > "$cgroup/addon/cgroup.procs" || true + if echo "+cpu +memory" 2> /dev/null > "$cgroup/cgroup.subtree_control"; then + delegated=true + break + fi +done + +if [[ "$delegated" != true ]]; then + bashio::log.warning "Could not enable the cgroup v2 controllers: cpu_limit and memory_limit are not applied" +else + if [[ -n "$cpu_limit" ]]; then + awk -v cpus="$cpu_limit" 'BEGIN { printf "%.0f 100000\n", cpus * 100000 }' > "$cgroup/addon/cpu.max" + bashio::log.info "CPU limited to $cpu_limit core(s)" + fi + if [[ -n "$memory_limit" ]]; then + echo "$memory_limit" > "$cgroup/addon/memory.max" + # As with docker --memory: as much swap again, so idle sessions can still be swapped out + echo "$memory_limit" > "$cgroup/addon/memory.swap.max" + bashio::log.info "Memory limited to $memory_limit" + fi +fi + +umount "$cgroup" +rmdir "$cgroup"