* skill(hassio-addon-workflow): deduplicate, route traps.md by section, fix preflight path Reviewed the skill against Anthropic's skill-authoring guidance (progressive disclosure, single ownership of each rule, explain the why rather than issue rigid rules) with a second, independent pass from Codex (gpt-5.6-sol). Real defect fixed: preflight.sh defaulted its repo argument to the hardcoded /data/claude/hassio-addons, so when run from a worktree it inspected the main checkout and reported that branch — the exact stale-checkout trap the script exists to catch. It now defaults to `git rev-parse --show-toplevel`. Deduplicated, one owner each: - measurement methodology: evidence.md owns it; traps.md's Measurement section is gone (its rtk note moved to Environment and workspace) - defensive-branch reachability: the standing rule owns it; step 3 points back - subagent delegation: SKILL.md's delegation note owns it; codex-review.md points - review-ratchet handling: SKILL.md step 6 owns it, absorbing codex-review.md's "this is wrong" vs "this is undefended" split - post-merge revert mechanics: traps.md owns them; step 9 states the check only - simplify checklist: step 5 owns the questions; simplify.md keeps the cases - version format: CLAUDE.md owns it; traps.md keeps only what it does not say Corrections found by review and verified against the workflows and scripts: - Super-Linter runs on every PR (lint.yml `pull_request`), not weekly; it is continue-on-error at both call sites, which is the part that matters - the three hard gates are matrixed over changed add-ons, so they skip rather than pass on a PR touching no top-level config.* - `--sandbox read-only` blocks writes, not reads or command execution; Codex can and does fall back to fetching the repo from GitHub - env_trace.sh's process argument is optional; pr_review.sh's reply and resolve take more than a PR number Also: step 4 now routes to the traps.md section by anchor instead of asking for the whole ~290-line file, and step 5 gains a Depth check (fix the shared mechanism once more than one add-on hits it) adapted from the built-in /simplify skill's altitude pass. No new markdownlint findings; shellcheck clean; preflight.sh verified reporting this worktree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * skill(hassio-addon-workflow): make step 4's traps.md routing actually save the tokens The routing table added in the previous commit was advisory only: a markdown anchor cannot be loaded on its own, so an agent told to read references/traps.md#shell-and-bashio reads the file and pays for all 18 KB. Measured overpay per edit, against what each section actually needs: shell / bashio 642 B needed, 18,070 B paid (28x) Dockerfile / arch 2,655 B needed, 18,070 B paid (7x) base-image env option 3,070 B needed, 18,070 B paid (6x) "CI and review bots" is 6,302 B of that — 35% of the file — and is needed at steps 7-8, never at step 4. scripts/traps.sh prints one section. Scripts are run rather than read, so the helper itself costs no prompt tokens and the table now buys what it claims. Kept traps.md whole rather than splitting it into six files: the split would save the same bytes but fragments a file with a working table of contents and breaks the references/traps.md#anchor cross-references that steps 5, 7 and 9 still use. Ambiguous or unmatched keywords list the sections and exit non-zero rather than silently printing the wrong one. Verified: every keyword in the step 4 table resolves to exactly one section, headings containing '/' work, and the last section reaches EOF. shellcheck clean; no new markdownlint findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * skill(hassio-addon-workflow): fix review findings — PATH form, preflight fallback, config.json claim All three reproduced before accepting. Step 4's routing table used a bare `traps.sh <keyword>`, which is not on PATH: every row failed with "traps.sh: command not found" from the repo root. Flagged independently by Codex, Copilot and CodeRabbit. Now uses the `bash "$SKILL/scripts/traps.sh"` form already established above it; all five keywords verified to run and resolve to exactly one section. preflight.sh fell back to the hardcoded /data/claude/hassio-addons when `git rev-parse` could not answer, recreating the stale-checkout bug this commit series set out to fix — and worse than CodeRabbit described: from a non-git directory it reported an unrelated branch of the main checkout as though it were the caller's own. It now refuses and asks for an explicit path (exit 1). Explicit-argument and in-worktree behaviour unchanged, both re-verified. Codex correctly noted the description advertised config.json while preflight.sh and validate.sh parse only config.yaml. Exactly one add-on uses config.json (zzz_archived_tor, archived) against 136 using config.yaml, so the proportionate fix is Codex's own alternative — narrow the description — rather than teaching two scripts JSON for an archived add-on. config.json dropped from the description. shellcheck clean; no new markdownlint findings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
4.1 KiB
Evidence — measurement methodology and case studies
How to read the numbers
Summed RSS overstates savings. Shared library pages are counted once per process, so removing a duplicate frees its private memory, not its RSS. Measured example: four MCP shims summed to 882 MB RSS but 643 MB PSS / 564 MB private, and per-process private ranged 54 MB down to 2 MB — which completely changes which duplicate is worth removing. Quote private when arguing "removing this saves N MB".
A large mapping is often not resident. SysV/tmpfs segments are lazily populated. Xvfb's
506 MB framebuffer shows Rss: 0 in /proc/<pid>/smaps. Check before calling anything a leak.
/proc/meminfo and free show host figures — there is no memory cgroup namespace here.
Never attribute those totals to the add-on.
A short CPU sample is not a CPU measurement. A 3 s sample measured 2.3% where a 20 s sample measured 21.6% for the same process. Use >= 20 s for anything you report.
scripts/measure.sh already reports PSS and private alongside RSS, and resident separately from
reserved, so these three only bite when you compute a figure yourself or quote one from ps.
Before asserting anything, ask what would show it false
- "This process is duplicated" → is it?
ps -ef --forest, compare parents and start times. - "This costs 500 MB" → is it resident?
grep Rss /proc/<pid>/smaps_rollup— plainsmapsprints oneRss:line per mapping (dozens of them), not a process total. - "This block never runs" → is its payload in the image?
command -v,apthistory. - "The flag isn't set" →
tr '\0' '\n' < /proc/<pid>/cmdline.
When you correct yourself mid-analysis, keep the correction visible in your notes and in what you report — a retracted claim that stays retracted is worth more than one quietly dropped.
The failure mode this loop keeps producing
Every bug shipped from the source session came from one move: measuring this host correctly, then generalising it to all hosts.
/dev/shmwas 7.7 GB here, so a flag looked useless — but Home Assistant ignoresshm_size, so elsewhere it is Docker's 64 MB default and removing the flag reintroduces a crash loop.- An MCP entry was identified by its URL — but that URL is the documented default, so the rule would have deleted a user's hand-written configuration.
- A GPU probe created a hardware context — but that proved the driver worked, not that Chromium's GPU path did.
- SABnzbd's source was grepped to see which proxy headers it reads, and
X-Forwarded-Forwas forwarded because it reads that one — butverify_xff_headeris on by default and makes it reject every address in the chain that is not local, so ingress answered 403 for anyone reaching Home Assistant from outside the LAN (#3019, fixed in #3023). Every check ran from inside the container, where no such header exists. That an app reads a header is not a reason to send it — find out what it does with it, and exercise the path a remote user takes.
The pattern is always inference standing in for detection. Before changing a default, ask what this is like on a host unlike yours. Prefer detecting the condition at runtime over asserting it. When ownership matters, record it rather than infer it.
"Merged and inert" — CI passing proves the build works, not that the change does anything
Both changes in the session that produced this skill passed CI, merged, and were inert:
- The Xvfb resolution cap wrote its env file correctly and Xvfb still started at the base-image default — wrong env mechanism for that service.
- The GPU flags reached Chromium's command line exactly as intended, and the GPU process still
reported
--use-gl=disabled, having overridden them after its own init failed.
Once the rebuilt add-on is running, re-run the measurement that motivated the work. Some fixes
cannot be self-verified — a service that reads its environment only at start makes an env-var fix
unproven until the add-on restarts, which needs the user or ha-cli with their agreement. If you
cannot restart, the change is Assumed, not Verified, and must be reported that way.