Skip to content

Claude session options, permissions guard, and new HA CLIs/skills - #1

Open
Tyderion wants to merge 36 commits into
mainfrom
feat/claude-session-options
Open

Tyderion wants to merge 36 commits into
mainfrom
feat/claude-session-options

Conversation

@Tyderion

Copy link
Copy Markdown
Owner

Everything the fork carried on top of jantimon/main, collected onto one branch now that main tracks upstream again (35 commits).

What's in it

  • Claude session options: claude_* add-on options, a claude wrapper that pins the session name, Claude Code updates persisted under /data, a claude_remote_control option, native installer instead of the deprecated npm package.
  • Permissions and safety: widened default permission set, auto permission mode, a destructive-operation guard hook, confirmation prompts for destructive commands, asks before touching secrets.yaml or writing into .storage, file rules anchored at the filesystem root.
  • New CLIs and skills: ha-trace/DebugAutomation, ha-history/QueryHistory, ha-template/TestTemplate, ha-service/CallService (with target validation and virtual-state refusal), ha-state/SetState, ha-entities update/rename/refs with UpdateEntity and EntityReferences.
  • Fixes to existing skills: EditDashboard, HAEntities, HomeAssistantReload, and ha-dashboard migrated onto a hardened shared ha_lib.
  • Build and CI: base image bumped to 21.0.5, pixi fmt-check in CI, repository URLs pointed at the Tyderion fork, CLAUDE.md template folded in from a live install.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci

Tyderion and others added 30 commits August 4, 2026 18:18
…fer destructive ops

- ha-dashboard set now takes a required file argument (was stdin-only,
  so the documented set workflow failed); stdin input is removed entirely
  so the only syntax is `ha-dashboard set <url_path> <file>`
- delete/update resolve the real dashboard id via lovelace/dashboards/list
  instead of guessing url_path.replace("-", "_"), which could target the
  wrong dashboard
- validate config shape (object with views list or strategy) before push
- catch connection/auth errors in main() instead of dumping tracebacks
- error on update without any metadata flags instead of no-op "success"
- render HA error message/code instead of a raw dict; drop dead --host flag
- list shows the addressable "default" main dashboard
- SKILL.md: backup-before-set/delete rule, file-path-only rule (no pipes),
  default url_path, gotchas for config_not_found and strategy dashboards,
  broader trigger vocabulary, honest concurrency note;
  CLAUDE.md.template: dashboards section

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- replace the TOON npx pipeline with PyYAML output (yaml default,
  --format json opt-in): measurements showed YAML beats TOON on this
  tool's shapes, and this drops the unpinned runtime npm fetch, the
  missing-npx traceback, and the no-timeout hang entirely
- declare pyyaml in requirements.txt (already shipped via yamllint)
- list: default --limit 100 (0 = unlimited), returned/truncated fields,
  count renamed to total, zero-match hint listing valid domains/areas
- list: --search substring filter over entity_id and name, multi-value
  --domain, case-insensitive domain/state filters, --exclude-unavailable
- check success on every WS response instead of silently returning
  wrong data (e.g. all areas null with a non-admin token)
- get: always returns a list, exits 1 when no entity resolves
- top-level error handler: one-line errors instead of tracebacks
- slim output: omit empty key_attr/area, cap unknown-domain attribute
  fallback to device_class/unit_of_measurement/state_class, add sensor
  device_class; symlink-safe realpath import; argparse parent/func cleanup
- SKILL.md: document yaml/--format, corrected output shapes, search-first
  and token-cost guidance; CLAUDE.md.template: Find Entity IDs section

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ha-reload: capture curl failures with a diagnostic instead of dying
  silently under set -e; add --connect-timeout 5 --max-time 120 so a
  wedged HA cannot hang the script forever; reject arguments (exit 2)
  and support -h/--help so `ha-reload --help` no longer triggers a real
  reload; send an explicit '{}' JSON body; guard non-numeric http_code
- honest success message: reload_all returns HTTP 200 even when a domain
  fails to load, so the script now points at 'ha core logs' instead of
  enumerating domains
- remove the false "validates first" claim from DOCS.md and
  CLAUDE.md.template — the script never validated; validation is a
  separate documented step
- reconcile the four divergent "what gets reloaded" lists: SKILL.md's
  detailed list is canonical, script/DOCS/template point to it
- SKILL.md: trigger now covers "restart Home Assistant" requests (check
  whether a reload suffices first), right-sized validation (yamllint
  always, ha core check only for configuration.yaml/pre-restart),
  all-or-nothing note, new Troubleshooting section, trimmed examples
- DOCS.md: fix skill invocation name (/HomeAssistantReload) and Claude
  persistence path (/share/.claude, not /data/.claude)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- WS.recv now reads the FIN bit and opcode: replies to server pings
  with pongs, ignores pongs, raises a clean error on close frames, and
  reassembles fragmented messages (previously any control frame or
  fragmented get_states payload crashed json.loads with a traceback)
- WS.close sends a proper close frame (code 1000) so HA stops logging
  an abnormal disconnect for every CLI invocation
- ha_call raises RuntimeError on success:false with HA's message and
  code, bounds its receive loop with a 60s deadline, converts
  socket.timeout into a descriptive error, and no longer mutates the
  caller's command dict
- _ensure_connection no longer caches a broken, unauthenticated socket
  as the singleton when the handshake or auth fails
- new ha_result() helper returns the unwrapped result; ha-entities drops
  its local success-checking wrapper and imports it

Verified with socketpair protocol tests: ping/pong echo, 3-frame
fragmentation, close handling, success:false raise, id skipping,
timeout error, close-frame emission.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Delete the private get_token/WS/ha_call copies (~110 lines) and use
ha_lib.ha_result like ha-entities does, per the repo convention. The
dashboard tool thereby inherits the hardened WebSocket client
(ping/pong, fragmentation, clean close) and the raise-on-failure
contract; the per-call success checks and _fail helper are gone, and
delete/update now reuse one singleton connection instead of opening a
second one for the id lookup. Connect timeout changes from 10s to the
lib's 30s (irrelevant on localhost).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bash(ha *) auto-approved ha core restart (a 30s+ outage and the Reload
skill's documented fallback), ha host reboot/shutdown, ha backup
restore, and more without any prompt — and the allowed supervisor POST
curl could do all of that plus call any HA service (unlock locks,
disarm alarms) promptless.

- settings.json: new permissions.ask list — ask rules override allow,
  so ha * stays promptless for safe commands while ha core
  restart/stop/update, ha host reboot/shutdown, ha backup(s) restore,
  ha addons uninstall/stop, ha os/supervisor update, and supervisor
  POST curls always prompt first; the POST curl pattern is removed
  from allow (GETs remain)
- init-user: merge permissions.ask additively like allow/deny, so
  existing installs (whose persisted allow lists still contain
  Bash(ha *) and the POST curl) get the same gating on next start
- DOCS.md: document the always-ask set and the additive merge

Verified: settings.json parses; jq merge simulation against an
existing-install settings file yields the full ask list while
preserving user-added allow entries and other settings.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shared app-ci workflow covers shellcheck/hadolint/yamllint/prettier
but never runs the repo's own tooling: the Python CLI tools shipped in
rootfs (ha-entities, ha-dashboard, ha_lib) got no ruff in CI and shfmt
formatting was local-only. Add a lint-fmt job (SHA-pinned setup-pixi)
running `pixi run fmt-check`.

Also drop --fix from the fmt-check-py task: a check task that silently
rewrites import order can never fail on it, so CI would not have
flagged unsorted imports.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The npm package is deprecated as of 2.1.198 and the image was pinned to
2.1.74 from March; the runtime auto-updater was silently papering over
the stale pin by updating the npm global tree in place.

- Dockerfile: install via the official installer
  (claude.ai/install.sh) pinned to CLAUDE_VERSION=2.1.221, symlink
  /root/.local/bin/claude to /usr/local/bin so PATH resolution stays
  identical (the installer's own profile edits are overwritten by
  COPY rootfs /); add libgcc and ripgrep, the documented musl
  requirements; nodejs/npm stay for npx-based MCP servers
- new /etc/profile.d/claude.sh exports USE_BUILTIN_RIPGREP=0 — the
  bundled ripgrep is glibc-only and does not run on Alpine
- auto-updates stay enabled by design: the native updater keeps users
  on the latest version between add-on releases, same lifecycle as the
  npm-era behavior (survives restarts, refetches after image rebuilds)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Swap jantimon/ha-addon-ssh for Tyderion/ha-addon-claude-code in
repository.yaml, config.yaml, README, and DOCS so this fork can be
added as a custom add-on repository in Home Assistant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fork kept the upstream repository name, making it
indistinguishable from the original jantimon repo in the Home
Assistant store UI. A distinct name makes the already-registered
Tyderion repository identifiable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rebuilds fail with apk exit code 5: base 20.0.1 pins musl=1.2.5-r21
in /etc/apk/world, but the Alpine 3.23 index has rotated to
musl-1.2.5-r23, so installing build-base (via musl-dev) is
unsatisfiable. Verified on both amd64 and aarch64 images against the
live Alpine indexes.

Base 21.0.1 (Alpine 3.24.1) pins musl=1.2.6-r2, which matches the
current 3.24 index exactly; a full local amd64 build of the add-on
succeeds with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The native installer's `claude install` step runs at build time and
leaves /root/.claude.json and /root/.claude/ in the image. init-user
then fails twice at startup: `ln -s` for .claude.json exits 1 with
"File exists" (stopping the whole container), and the .claude
directory link is silently created inside the existing directory,
breaking persistence.

Fix in two layers: the Dockerfile removes the installer state after
install, and init-user clears both paths before creating its symlinks
so future installer changes cannot crash the add-on again. Verified in
a full local build: image contains no /root/.claude*, claude --version
works, and the rm+ln sequence produces correct symlinks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New skill for answering "why didn't my automation fire?" via HA's
execution traces (trace/list, trace/get over the WebSocket API):

- ha-trace list: all automations/scripts with stored trace counts and
  the latest run's outcome, traced items first
- ha-trace runs <target>: per-run summaries (trigger, outcome,
  last_step, error), with a guidance hint when no traces exist
- ha-trace show <target> [--run ID] [--full]: condensed step-by-step
  walkthrough (per-step result, offset, error; variables dropped for
  token efficiency), --full for the raw trace

Targets resolve from entity_id or friendly name across both domains;
YAML automations without an id: field get an explicit error since HA
records no traces for them. SKILL.md documents the debugging workflow,
outcome glossary, and the traces-only-exist-for-started-runs gotcha.
CLAUDE.md.template gains a Debug Automations section.

Verified with stub-based functional tests covering all commands,
name/entity resolution, ambiguity, no-id, and empty-trace paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New skill for past-tense questions ("when was the door last opened",
"how long was the light on", "average temperature yesterday") via the
recorder's WebSocket API:

- ha-history changes: state-change log (history/history_during_period)
  with consecutive-duplicate merging, per-change durations, a
  time-in-state summary over the full window (totals + percent), newest
  changes first with --limit 50 default; numeric-looking entities get a
  hint to use stats instead
- ha-history stats: long-term statistics buckets
  (recorder/statistics_during_period) with per-entity unit resolution;
  mean/min/max for measurements, sum/state/change for metered entities
- ha-history stats-list: discover which entities have statistics, with
  kind (mean/sum), search and limit

Windows are relative (--last 30m/8h/3d/2w) or absolute ISO --from/--to
(local time unless an offset is given). SKILL.md explains changes vs
stats (raw history purged ~10 days; statistics forever but numeric
only) and the window-edge semantics. CLAUDE.md.template gains a Query
History section.

Verified with stub-based functional tests covering merging, summaries,
limits, window parsing errors, partial/missing entities, rounding,
unit fallbacks, and stats-list kinds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two new read-only query tools were missing from the default
permission allow list, so every invocation would prompt. Same pattern
as ha-entities; existing installs pick the entries up via the additive
init-user settings merge on restart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude Code checks file permissions against Edit() and Read() rules
only; Edit(//tmp/*.json) already covers the Write and NotebookEdit
tools, and a Write() path rule is never consulted and triggers a
startup warning since v2.1.208.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Render Jinja templates against live HA state over the WebSocket
render_template API before they land in automations, dashboards, or
template sensors.

- ha-template: inline or --file input, --var KEY=VALUE (JSON-parsed)
  for trigger/script variables, --watch to stream re-renders on state
  changes, --timeout, yaml/json output. Shows `listeners` (what the
  template subscribes to) — the key debug signal for stale templates.
  Template errors go to stderr with exit 1 via report_errors.
- ha_lib: new ha_subscribe() generator for subscription-style commands —
  consumes the ack, yields event payloads, keep-alive pings on idle
  socket timeouts after the first event.
- TestTemplate SKILL.md with verify-before-config workflow and gotchas,
  CLAUDE.md.template section, settings.json allow entry.

Stub-tested: 16 tests covering one-shot/watch output, listener
trimming, var parsing, input validation, and ha_subscribe framing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Discover services with their real field metadata (stops hallucinated
parameters) and call them over the WebSocket API with guarded access to
dangerous services.

- ha-service list: compact domain map, or detailed rows with
  --domain/--search; marks services that return responses.
- ha-service show: full field spec with selectors/required/examples,
  flattening collapsed field sections; target domains; danger marker.
- ha-service call: --entity/--area/--device/--label targets, --data
  KEY=VALUE (JSON-parsed) plus --data-file (flags win), validates the
  service exists with did-you-mean hints, warns on unknown data keys,
  auto-requests responses for services that return them.
- Dangerous services (homeassistant.restart/stop, hassio.*,
  recorder.purge*, shell_command.*) refuse to run without --unsafe.
  settings.json asks on any command containing --unsafe (doc-verified:
  ask rules beat allow rules; Bash wildcards match at any position), so
  the prompt cannot be dodged by flag reordering. Normal calls flow
  under the new Bash(ha-service *) allow entry.
- CallService SKILL.md, CLAUDE.md.template section.

Stub-tested: 18 tests covering list/show/call, section flattening,
response auto-request, danger gating, data merging, and errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A call targeting a nonexistent entity either fails with HA's cryptic
"did not match any entities" (response services) or silently does
nothing (normal services). Check --entity ids against get_states first
and fail with did-you-mean suggestions (difflib, same-domain fallback)
plus a pointer to ha-entities. Area/device/label targets are unchanged.

Found live: calling weather.get_forecasts against an entity id that
does not exist on the box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A deployed session ran `./ha-entities` instead of the bare name: the
relative path missed the `Bash(ha-entities *)` allow rule (forcing a
manual approval), failed because the tool is not in the working
directory, and the session then bypassed the CLI with an ad-hoc ha_lib
script.

Three layers against a repeat:

- CLAUDE.md.template: new "CLI Tools" section stating all ha-* tools
  are on PATH, must be invoked by bare name, and that a failing call is
  fixed rather than worked around with ad-hoc ha_lib scripts.
- Every SKILL.md: same instruction inline, right after the intro.
  Skills are re-copied on every add-on start, so this reaches existing
  installs without user action.
- settings.json: deny rules for `./ha-<tool>` forms (one per tool, not
  a blanket `./ha-*`, to spare user-owned scripts). The deny list is
  merged into existing installs on every start, turning the mistake
  into an instant, named denial instead of a permission prompt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reviewed against a real, long-lived instance file and pulled in
everything that applies to any install, with no instance identifiers:

- Environment: /homeassistant vs /config path duality (which prefix to
  use where), and reading versions via `ha core info` / `ha os info`
  instead of hardcoding them.
- "Discover, Never Guess": question -> skill routing table plus
  ha-entities starting points, making look-it-up-first the headline
  rule rather than a per-task footnote.
- Custom component enumeration via manifest.json as a Common Task.
- Best practices: verify automations with traces, numeric script IDs,
  UI group-helper entities, multiple radio stacks, and seasonal
  `unavailable` devices not being faults.

Instance-specific content (domain knowledge, config layout, git
tracking, specs workflow) deliberately stayed out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Safe mutation of virtual state: helpers (input_*), counter, timer, the
var integration, and the device-backed select/number/text domains.

`ha-state show <entity>` prints the current state plus exactly which
values `set` accepts (select options, number min/max/step, timer
keywords, datetime shape). `ha-state set <entity> <value>` resolves the
right service for the domain, validates the value against the entity's
own constraints before calling, then reads the state back and reports
before/after with a verified flag — a mismatch prints a note and exits
1 so a silently ignored write cannot pass as success. Device command
domains are refused with a pointer to ha-service, and the tool never
touches HA's raw set-state API.

Wiring: allow rule `Bash(ha-state *)` plus the `./ha-state` deny
backstop, template routing-table row, Set Helper State common task, and
the tool added to the PATH lists.

21 stub tests green (validation, per-domain service resolution,
read-back verification, refusals).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`ha-service call` on input_*, counter, timer, and var now exits with a
redirect to `ha-state set`, the same deterministic-gate pattern as the
--unsafe check: a raw call on those domains has no value validation and
no read-back, and ha-state covers every blocked call with both. The
administrative `reload` and `configure` services are exempt.

The CallService skill gains a Not For section drawing the tool
boundaries explicitly: state reads belong to HAEntities, templates to
TestTemplate, history to QueryHistory, virtual state to SetState, YAML
reloads to ha-reload.

Gate covered by the stub tests (refusal message, reload/configure
exemptions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Everything steering the in-container Claude was prose in CLAUDE.md.template,
so it held only as long as the model remembered it. Move the two rules that
break things when forgotten into hooks, and stop making the model ask for
permission to do ordinary read-only work.

- PreToolUse[Bash] rewrites path-form ha-* calls (./ha-entities,
  /usr/local/bin/ha-service, python3 ha-state.py) to their bare PATH names.
  Those forms miss the allow rules, prompt the user, and push Claude toward
  hand-rolled ha_lib scripts. Rewriting is limited to command position, so
  reading or grepping the tool sources is untouched. The matching deny rules
  stay as a backstop in case a Claude Code build ignores updatedInput.

- PostToolUse[Write|Edit|MultiEdit] parses edited YAML under /homeassistant
  with a loader that tolerates HA's ! tags and rejects duplicate mapping
  keys, then hands failures back to Claude as a tool error. Syntax only;
  yamllint still owns style.

- Permissions: add Write alongside Edit, Glob/Grep, colon-form ha-* rules so
  a bare no-arg call matches, and the read-only shell and git verbs. sed is
  deliberately left out so edits route through the tools the validator
  watches. Deny writes to .storage, and raise the Bash timeout, since
  `ha core check` outruns the default on slow hardware.

- init-user only merged `permissions` into an existing settings.json, so
  hooks and statusLine would never have reached an existing install. It now
  also carries statusLine and hooks (image wins, they point at /etc/claude)
  and env (user wins).
Claude prompted for approval on nearly every command it ran inside the
add-on. The cause was the permission model, not the rule list: with no
defaultMode set, Claude Code runs in `default` mode, where anything not
matched by an explicit allow rule asks. The shapes Claude actually writes
here - heredoc python3, pipes, `cd x && y` - can never match a prefix rule,
so widening the allow list could not have fixed it.

The trust boundary is the container, not the individual command. This add-on
runs as root and holds a Supervisor token, so anything that can reach the
Supervisor can already restart Core and read every secret. So describe the
container to Claude and let it approve routine work itself, while keeping an
explicit gate on the operations that are genuinely consequential.

- settings.json: `permissions.defaultMode: "auto"` plus an `autoMode` block
  describing this container, its mapped directories, and the bundled ha-*
  tools. Restarts, reboots, backup restores, Supervisor POSTs and secrets.yaml
  stay in `ask`; .storage edits stay denied.
- Convert every `Bash(cmd *)` rule to the `Bash(cmd:*)` prefix form. The
  space-glob form is undocumented and does not appear to match, which is why
  the ha-* tools already carried both forms. Retired rules are listed in
  settings-stale.json and pruned from users' persistent settings on start,
  since the additive merge can never drop one on its own.
- hooks/guard-destructive.sh: a PreToolUse guard that blocks recursive
  deletes of a mapped directory, mkfs, dd onto a device node, piping a
  download into a shell, and shell writes into .storage. Hooks run even under
  bypassPermissions, so this is the floor that holds when the permission rules
  are switched off. It deliberately does not cover restarts or reboots; those
  are things the user legitimately asks for.
- config.yaml: new `claude_permission_mode` option (default|acceptEdits|auto|
  bypassPermissions, defaulting to auto), validated in init-user/run and
  written into permissions.defaultMode on every start.
- init-user/run: the settings merge now treats autoMode as image-managed,
  fills skipAutoPermissionPrompt as a user-overridable default, applies the
  permission-mode option, and prunes stale rules. Without this, none of the
  new keys would reach an existing install.
- ssh/tests/: table test for the guard (35 cases), wired to `pixi run test`
  and a new CI job.

The autoMode schema is modelled on a working local Claude Code install rather
than on published documentation, so it is unverified against the build inside
this image until the add-on runs.
Replaces four init_commands workarounds with real add-on options:

- claude_session_name: /usr/local/bin/claude is now a wrapper that passes --name to every session, so Claude never re-derives the name. Subcommands (read from the binary's own --help, cached per version) and an explicit -n/--name pass through untouched.
- claude_continue_session: the first auto-start in the web terminal's tmux session continues the last conversation. SSH logins, extra tmux windows and a manual claude start fresh, so two processes never share one conversation.
- claude_autostart_session: ttyd/run starts the tmux session at boot so Remote Control works without opening the web terminal. Off by default because of the idle memory cost.

Claude Code's versions directory now lives in /data/claude-versions (excluded from backups), so auto-updates survive restarts instead of rolling back to the bundled version. The bundled binary is copied only when it is newer than everything persisted; an old /share/.claude-versions is migrated once. Bash now gets the same cd + auto-start as zsh via .bashrc.

Options get UI labels via translations/en.yaml. The wrapper has a table test wired into pixi and CI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
Base 21.0.1 pins libcrypto3/libssl3 3.5.7-r0 in /etc/apk/world, while the Alpine 3.24 repository has moved to openssl-dev 3.5.8-r0, so the build-dependencies install fails with "unable to select packages". 21.0.5 pins 3.5.8-r0; the full package list resolves against it (checked with apk --simulate on amd64).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
claude doctor warns that the native install dir is not on PATH and suggests prepending it, which would put the real binary ahead of the /usr/local/bin/claude wrapper and silently drop the pinned session name. Appending it instead clears the warning (verified on the device) and keeps the wrapper first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
Claude Code 2.1.280 no longer appends a collision suffix: two live sessions launched with the same --name both register it with nameSource "user" (checked on the device). Document that, and how to name one session differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
The shipped settings.json is user settings (~/.claude/settings.json), where a single leading slash anchors at ~/.claude, not at /. So Edit(/homeassistant/**) matched /root/.claude/homeassistant/**, and every path rule was dead: the allow rules, the secrets.yaml ask rule and the .storage deny (the guard-destructive hook still blocked .storage edits on its own). All Read/Edit rules now use the // absolute form.

Write rules are dropped: Claude Code warns at startup that file permission checks only match Edit rules, which cover all file-editing tools. The old single-slash and Write rules go into settings-stale.json so the start-time merge prunes them from existing installs (checked by running the merge filter against the previous defaults).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
Tyderion and others added 5 commits September 23, 2026 16:28
The option sets remoteControlAtStartup in the persistent settings on every start (option-driven, like defaultMode), so interactive sessions connect to Remote Control without running /rc. With claude_autostart_session the session is reachable from the Claude app right after boot.

The wrapper now always passes `claude remote-control` through: it is not listed in --help, so it would have received --name, and the Remote Control docs say it refuses to start when a wrapper adds a global flag in front of it. Covered by new wrapper test cases.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
The secrets.yaml ask rules only cover the Read and Edit tools. On the device, Claude created and wrote the file with a shell command instead, and auto mode approved it as routine /homeassistant work, so no prompt appeared. A new PreToolUse[Bash] hook returns an "ask" decision for any command naming secrets.yaml; per the hooks docs a hook ask forces the prompt in auto mode too. Commands that reach the file without naming it (grep -r over /homeassistant) are not caught, which DOCS.md now says.

Hooks are image-managed in the settings merge, so existing installs pick it up on the next start. Table test wired into pixi and CI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
guard-destructive.sh hard-blocks a fixed list of shell writes into .storage (tee, sed, cp, mv, redirects), but python, rm, rsync, ln, xargs or `cd .storage && ...` got through, and auto mode approves them as routine work. ask-secrets.sh becomes ask-sensitive.sh and covers both files: secrets.yaml asks on any mention as before; a command naming .storage passes only when every part of it (split on ; & | $( ) and backticks) is a known read-only command and output is redirected only to /tmp or /dev/null. Everything else asks, so reading the registries stays prompt-free but nothing writes HA state without the user seeing the command.

Parsing drops single-quoted text first (where jq filters keep their | and >), keeps double-quoted text so a $( ) inside it still counts, and asks on an unterminated quote. Table test extended to 36 cases, including quote-hiding and cd-then-write attempts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
There was no sanctioned way to rename an entity or move it to an area, so Claude would reach for the UI or .storage/core.entity_registry. `ha-entities update` changes name, area, icon, hidden and disabled through config/entity_registry/update, or name and area on the device with --device (config/device_registry/update). Areas resolve by name or id, unchanged fields are not sent, --dry-run previews, and each changed field is printed before and after with a read-back check that exits 1 on a mismatch. Entities without a unique_id get an explanation instead of a failure.

`ha-entities rename` changes an entity_id (same domain, target must be free) and lists every YAML file, UI dashboard and UI helper that still names the old id, since Home Assistant does not rewrite them. ask-sensitive.sh prompts for a rename unless that command segment is a --dry-run, so the reference listing stays prompt-free. The WebSocket fields and responses were checked against home-assistant/core.

New stdlib-only unit tests run the tool against a fake registry (17 cases, PyYAML optional), wired into pixi and CI; the hook table grows to 44 cases. The UpdateEntity skill, the HAEntities skill, the CLAUDE.md template and DOCS.md point at the new commands.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
`ha-entities refs <entity>...` answers "what uses this?" on its own, not only as a rename dry-run. It merges Home Assistant's search/related (the UI's Related tab: automations, scripts, scenes, groups, persons, including device triggers; upward relations like device and area are dropped) with the existing text scan of the YAML config and the UI dashboard/helper stores, which also finds templates and dashboards that search/related does not cover. `--device` expands to every entity of the device plus references by device_id. rename now reports the Related-search hits too. search/related was checked against home-assistant/core.

The EntityReferences skill triggers on "what uses / is it safe to rename, remove or replace"; UpdateEntity now runs refs before any rename, and says that renaming a device alone breaks nothing. The CLAUDE.md template and DOCS.md point at it, and the stale DOCS "Built-in Skills" section (which listed one of ten) now lists them all. Unit tests grow to 23 with a faked search/related.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Comment thread .github/workflows/ci.yaml Fixed
Comment thread .github/workflows/ci.yaml Fixed
zizmor (artipacked) flagged the lint-fmt and test jobs: actions/checkout writes GITHUB_TOKEN into .git/config unless told not to. Neither job pushes, so drop it, matching the upstream reusable workflow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbctb8bNXdgkb7WT5hX5ci
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants