Files
hassio-addons/.claude/skills/hassio-addon-workflow/SKILL.md
Alexandre 9443356b36 docs(skill): ask "is this the simplest way possible" before implementing (#3062)
* docs(skill): ask "is this the simplest way possible" before implementing

Every question the loop asks — of me and of Codex — is about what could go
wrong, and each answer argues for more code. Nothing pushed the other way
until the maintainer asked, and by then the code existed.

On PR #3061 that cost a 309-line Python registry client to read one OCI
label, in a script whose neighbouring branch does the same kind of job in
inline curl and jq. The plan review and the code review both passed it.
Asked outright, Codex said to delete all 309 lines; the inline version came
to 21 and returned byte-identical output on every image tested. It is the
second time the question has had to come from the maintainer.

Step 3 now asks it before any code exists, of myself in writing and of Codex
as a question of its own with the smaller alternative sketched out to argue
for. Step 5 gains an Idiom check, on whether a hunk looks like the code it
sits beside. Adding a new file or a new mechanism now means the full loop
however small the diff looks, and codex-review.md records that Codex will
never raise proportionality unprompted but answers it bluntly when asked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs(skill): give the proportionality self-question a home on the light path

Three independent reviewers on #3062 (CodeRabbit, Codex, Copilot) all caught
the same gap: the self-question and its written answer lived only inside step
3, marked full-loop-only, while the light path went straight from scope to
implement. A light task following the documented sequence never reached the
instruction telling it to ask "is this the simplest way?" at all.

Moved the self-question (and the written answer it requires) into the shared
triage section, before the light/full-loop split, so both paths ask it before
implementing. Step 3 now only adds Codex to that question, for full loop.

Also: clarified that a version bump plus its CHANGELOG entry is release
bookkeeping, not a "new file", so it does not trigger the light-to-full-loop
escalation on its own (Codex's second finding on #3062, which read the
one-file threshold as counting every file the PR touches rather than whether
a new mechanism was added). Fixed step 5's stale "six questions" to seven,
after Idiom was added in the prior commit. Fixed simplify.md's case study to
say the 309-line client was "initially proposed", not shipped, matching the
prior PR's own body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(skill): split "test the smaller candidate" by whether the diff exists yet

CodeRabbit on #3062 caught that step 3's "build the smaller one and run it"
never says to compare it against the larger candidate, only to run it. As
written it applied at plan time, before either candidate has code — where the
larger candidate is a plan, not a diff, so there is nothing to compare against
without building both in full, which defeats the point of picking the cheaper
one first. Testing the smaller sketch against the actual requirement is the
correct and complete check there.

The comparison CodeRabbit is describing is real, but it happens at step 6, not
step 3: by the time code review runs, the larger candidate already exists as
the diff, so sketching the smaller one and diffing its output against the
shipped code costs nothing extra and is exactly what settled #3061. Moved the
build-and-compare instruction there instead of applying it, unchanged, to a
step where nothing exists to compare against yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 17:04:54 +02:00

17 KiB

name, description
name description
hassio-addon-workflow Workflow for alexbelgium/hassio-addons Home Assistant add-on work: diagnose with real measurements, independent Codex review, implement, open a PR, resolve CodeRabbit / Copilot / Codex bot review comments, verify in production. Use for any task touching an add-on in this repo — bugs, RAM/CPU/performance tuning, Dockerfile, config.yaml, build.json, updater.json, cont-init.d, services.d or s6 changes, version and CHANGELOG bumps, a failing add-on CI check, opening or iterating PRs — and, on an add-on task, when asked to "check with codex", "verify with chatgpt", or resolve bot comments. Cheap for small asks: a light path skips the heavy steps.

Home Assistant add-on workflow

Answer style. Chat replies are terse — no pleasantries, tool-call narration, decorative tables, emoji or dumped logs; quote the shortest decisive line and don't re-print what is already in context. Telegraphic fragments are fine there. Two things outrank brevity, because dropping a word from either changes the meaning rather than shortening it: never compress uncertainty markers ("likely", "assumed", "not verified"), negations, numbers, units, technical terms, code or error strings — step 9's Verified/Checked/Assumed distinction wins every time; and write full prose wherever a fragment could be read two ways — security warnings, irreversible-action confirmations, multi-step sequences, and everything persisted (commits, CHANGELOG entries, PR bodies, review-thread replies, the step 10 report).

Triage first, then one of two paths:

  • Light — typo/doc fixes, CHANGELOG edits, version bumps, one-file edits at ladder levels 1-3 (below), simple questions: scope → implement → validate (step 4) → PR (version bump + CHANGELOG still required) → resolve bot comments.
  • Full loop — performance/RAM/CPU work, diagnosis, anything changing a shipped default, ladder levels 4-6, or an explicit Codex-check request: scope → measure → plan → Codex reviews the plan → implement → simplify → Codex reviews the code → simplify again → PR → resolve comments → verify in production → report.

Before implementing, on either path, ask yourself the proportionality question and answer it in the plan (a one-line plan, for a light task) — in writing, naming the smaller version you rejected and why:

What is the smallest thing that solves this? What would you delete from what I just proposed? If it had to be written in ten lines, in the idiom of the code it sits beside, what would be lost — and is any of that a case I have actually demonstrated?

Escalate mid-flight if a light task grows — touches a default, needs a new script or service, reveals a deeper problem, or turns out to need more than the one file once you have honestly answered that question. A light task never skips it; it skips Codex only while the answer stays inside one file. Adding a new file or a new mechanism puts it on the full loop, however small the diff looks — a version bump and its CHANGELOG entry are release bookkeeping, not a new file for this purpose, and never trigger it on their own.

Standing rule: ship the simplest solution that works, and build it out of what already exists — a .templates/ module, an existing cont-init script, the pattern a sibling add-on already uses for the same problem. 120+ add-ons are maintained by one person: a homogeneous repo where every add-on solves a problem the same way is worth more than a locally nicer bespoke design. Prefer reusing or extending over adding a parallel implementation, and when you must add something new, spell it the way the rest of the repo spells it (naming, option names, script numbering, file layout). Complexity is bought only by a measurement showing a concrete, user-visible cost on a real host — never by reasoning about hypothetical performance, and never by reasoning about a hypothetical host either. A defensive branch is complexity like any other: name the input that reaches it and the image or host where that happens, or delete it and let the case fail visibly instead.

Skill root. The canonical copy lives at .claude/skills/hassio-addon-workflow/. Set it once; every scripts/… and references/… path below is relative to it:

SKILL="$(git rev-parse --show-toplevel)/.claude/skills/hassio-addon-workflow"
bash "$SKILL/scripts/preflight.sh"        # and likewise for the other scripts

Non-negotiables:

  • Docker build cannot be tested locally (no dockerd) — CI is the only gate.
  • Never git stash under /data/claude — refs/stash is shared across worktrees.
  • Work in a worktree under /data, not /tmp (/tmp is noexec).

Delegate heavy output to a subagent. Codex reviews and multi-thread PR triage produce output you don't need verbatim in your own context. For Codex's plan review (step 3), Codex's code review (step 6), and PR-comment listing when there are more than ~5 threads (step 8): if subagents are available, launch one to run the command and report back only the objections/findings and your assessment of each, not the raw transcript. Otherwise redirect the output to a file and read the parts you need.


1. Scope

State goal, non-goals, constraints, and definition of done — two sentences, explicit. A diagnosis ask ("why is it slow?") is not automatically a fix ask. Changing a shipped default is the user's call, not yours — ask before implementing.

2. Evidence before reasoning (full loop)

Measure the running add-on rather than reasoning from source ($BUILD_VERSION set, HOME=/data/data) — reviewers hold you to the numbers. Tool per question:

  • RAM/CPU → scripts/measure.sh (≥20 s sample)
  • "I set an option and nothing happened" → scripts/env_trace.sh <VAR> <process>
  • Is this flag/driver/package actually present? → inspect the artifact: /proc/<pid>/cmdline, command -v, /var/log/apt/history.log

Verify you're reading the right revision first — scripts/preflight.sh compares the checkout against the running $BUILD_VERSION, which catches the usual stale branch before it costs a full analysis pass (a version match does not prove the source is identical). Read references/evidence.md before interpreting any number you did not get straight from measure.sh, and for the failure modes this step exists to prevent.

3. Plan — choose the mechanism level, then Codex reviews it (full loop)

Look for prior art first: grep .templates/ and the other add-ons for something that already solves this (grep -rl "<knob or pattern>" --exclude-dir=.git . — search everything, not just *.sh: the mechanism may live in a Dockerfile's ARG MODULES= or an extensionless s6 run file). If an add-on already handles it, the plan is "do what that one does" — say so, and say why the existing mechanism can't be reused if you're not reusing it.

Then rank mechanisms, pick the lowest (simplest) one that solves it, and state the choice in the plan:

  1. A config value — an option, a schema constraint, an existing env var.
  2. An existing knob the base image already reads (MAX_RES, DRINODE, SELKIES_*).
  3. A few lines in an existing script, at the point that already runs.
  4. A new init script.
  5. A new service, wrapper, or long-running process.
  6. Custom protocol code, or patching someone else's internals.

Levels 4-6 need a reason that survives being said out loud ("upstream has no knob for this, and I checked" is one; "it felt cleaner" is not) and mean full loop.

Full loop adds Codex to the proportionality question already asked above — put it to Codex as a numbered question of its own, with the smaller alternative sketched out for it to argue for. Every other question in the loop asks what could go wrong, which only ever argues for more code; this is the one place that pushes the other way, and it is worthless unless asked outright — see references/codex-review.md. Nothing is built yet at this step, so "test" means against the real requirement, not against the other candidate — sketch the smaller one and check it against what the add-on actually needs; that sketch is what you go on to implement if it holds up, not one of two things you build. A sketch costs minutes and settles the argument with a measurement instead of a preference.

Attack your own plan before implementing:

  • What does this do on a host unlike this one — no GPU, small /dev/shm, aarch64, a VM?
  • What happens on upgrade to someone who configured this by hand?
  • What is the blast radius if the assumption underneath it is wrong?
  • What am I inferring that I could instead detect at runtime or record explicitly? Highest-yield question here — see references/evidence.md's failure-mode section.
  • For every branch that exists only to survive something going wrong: name the image or host where that input arrives (the standing rule above) and go and look, before you write it.

Full loop only, before writing code: get Codex's independent read on the plan, delegated as above — references/codex-review.md has the invocation and how to write the prompt. The proportionality question above goes in that prompt every time, and in the step 6 prompt too.

4. Implement

Read the references/traps.md section matching what you're about to touch. It is ~18 KB and all but one section is irrelevant to any given edit, so print the one you need rather than reading the file — run it with no argument to list the sections:

Touching Run
an option or anything a base-image service reads bash "$SKILL/scripts/traps.sh" passing
a file the app also writes itself bash "$SKILL/scripts/traps.sh" "app's own"
shell, bashio, a symlinked script bash "$SKILL/scripts/traps.sh" bashio
Dockerfile, build.json, an arch guard bash "$SKILL/scripts/traps.sh" dockerfile
Chromium, Electron, Xvfb bash "$SKILL/scripts/traps.sh" chromium

Then validate with scripts/validate.sh <addon> --vs-master, and write behavioural tests for anything with branches, targeting the regression a reviewer described, not just the happy path.

5. Simplify

Seven questions over your own diff, before anyone else reads it:

  • Level — did the diff stay at the ladder level chosen in step 3, or creep up one?
  • Deletion — can this be solved by deleting instead of adding?
  • Size — is the fix bigger than the thing it fixes?
  • Reuse — does any hunk reimplement what .templates/, another script in this add-on, or a sibling add-on already does? If a future add-on hits this problem, will it find one way to solve it or two? Fold near-duplicates in, or justify the divergence in the PR body.
  • Idiom — does this look like the code it sits beside? The same kind of job done two ways in one file costs every later reader more than the nicer of the two ways saves. Reuse asks whether the mechanism already exists; Idiom asks whether you wrote yours the way the neighbours are written.
  • Depth — is this a special case bolted onto shared infrastructure? Fix the shared mechanism instead once more than one add-on hits it; generalising from a single case is how bespoke designs get built, so below that bar the special case is the right call.
  • Longevity — how does this fail in three years, when the base image or upstream has moved?

The standing exception to Reuse and Depth: scripts shared by symlink with the webtop add-ons take a new numbered script, never an edit (references/traps.md#shell-and-bashio). Case studies for the rest, including what shipped when this pass was skipped: references/simplify.md.

6. Codex attacks the code, then simplify what the review added (full loop only)

Same delegated invocation, pointed at git diff origin/master...HEAD plus your reasoning per hunk. Details in references/codex-review.md.

Then run step 5's checks again over the hunks the review changed. Adversarial review mostly argues for another branch — that is what it is asked to do — so accepting objections tends to ratchet the diff upward, and nothing else in the loop walks it back down. Sort each objection before you write anything: "this is wrong" is a bug and you fix it; "this is undefended" is a claim about some host, and it needs the same demonstration you would demand of a measurement — is the case it defends one you have now demonstrated, or one you have merely been told about? Taking a correctness objection often deletes the code that made it necessary, and a fix that collapses back to fewer lines than you started the review with is the normal outcome, not a suspicious one.

Then ask the proportionality question again, of the diff this time — it is cheap, and by now there is real code to point at rather than a plan. This is where "build the smaller one and test both" is actually free: the larger candidate already exists as the diff, so sketch the smaller alternative and diff its output against the shipped code on the same inputs, the way #3061 was settled (references/simplify.md) — rather than against the requirement alone.

These edits land after step 4's checks already ran, so re-run them: scripts/validate.sh <addon> --vs-master plus the behavioural tests, over the final diff. Deleting a branch is exactly the kind of edit that leaves a stray fi behind.

7. Open the PR

Three hard gates — CHANGELOG.md updated, the HA add-on linter (frenck/action-addon-linter), and the add-on image build — but only on a PR that changes a top-level config.*. On a PR that doesn't (docs, .github/, .claude/) they skip, which is not the same as passing. Super-Linter runs on every PR and is continue-on-error, so it never blocks; fix its real findings anyway. Nothing checks the version bump, so bump it yourself — Supervisor won't offer a rebuild without one, and CLAUDE.md has the format. Update README.md if you added options; write the CHANGELOG heading as ## <version> (<date>), matching the date format already in that file — almost always ISO YYYY-MM-DD, see references/traps.md#ci-and-review-bots.

Write the body to a file, gh pr create --body-file: state what was measured, what changed, what is not verified, and how to roll back the riskiest hunk alone.

8. Resolve review comments

scripts/pr_review.sh list|status|watch <PR> to read, reply <PR> <COMMENT_ID> <text|@file> and resolve <PR> <THREAD_ID…|--all> to answer; run it with no arguments for the full usage. For every comment, reproduce the claim before agreeing or disagreeing — reviewers are frequently right and occasionally confidently wrong; a reproduction takes a minute and decides it either way. Reply with the evidence, then resolve. Push back when you're right, on the thread — a resolved-but-wrong thread is worse than an open one.

9. Verify before declaring done

Never blur these three: Verified (you ran it and observed the result), Checked (parses, lints, type-checks), Assumed (reasoning only — name the assumption). Do not write "this should work" — either it was exercised, or say plainly it wasn't.

Light path: verification is validate.sh plus CI; anything beyond that is Assumed. Full loop: CI passing proves the build works, not that the change does anything — re-run the measurement that motivated the work once the rebuilt add-on is running. Real "merged and inert" examples, and what to do when a fix cannot be self-verified: references/evidence.md. Then confirm the change survived the merge: git fetch origin master first (the tracking ref is stale otherwise), then git diff origin/master -- <the paths you touched> must come back empty. Ancestry is not the check, and the builder reverts merges for reasons unrelated to your diff — both explained in references/traps.md#ci-and-review-bots.

10. Calibrate and report

Close against the scope from step 1, not against what you ended up doing:

What was asked / what shipped   — mapped to the original scope
Evidence                        — the numbers, before and after
Verified                        — observed, with how
Not verified                    — and why (e.g. no dockerd locally; CI is the gate)
Known broken / left out         — explicitly, including anything descoped
Risk + rollback                 — the riskiest hunk and how to revert it alone

Lead with anything that did not work — a merged PR that achieved nothing is the single most important sentence in the report. Give confidence per claim, not one blanket number.

Feed the skill. When a shipped fix needed a follow-up PR, or a reviewer caught something this skill should have, add the distilled lesson to the matching references/ file in that follow-up PR — one entry, with the PR numbers. That loop is what keeps this file short and the traps real.

Scripts are meant to be run, not read — each is cited at its point of use above; read one only if its output surprises you.