skill(hassio-addon-workflow): fix the stage-3 trap's actual mechanism (#3058)

* skill(hassio-addon-workflow): fix the stage-3 trap's actual mechanism

traps.md attributed the shipped svc-xorg failure to that service's own shebang
("starts #!/usr/bin/env bashio, not with-contenv"). Verified against
.templates/ha_entrypoint.sh: that isn't what decides it. ha_entrypoint.sh
rewrites the first line of every cont-init.d script and every services.d/s6-rc.d
run script to one shebang chosen by probing candidate_shebangs in order. The
first candidate is /command/with-contenv bashio; in add-ons that override the
base ENTRYPOINT ["/init"] (confirmed live in ente, wger, free_games_claimer),
s6 stage 1 never runs, that candidate fails, and the probe falls through to
/usr/bin/env bashio for every service in the image, regardless of what shebang
it shipped with. So no service reads stage 3 in that case, not just svc-xorg.

Surfaced independently by two agents in an unrelated eval run for PR #3057 (both
named the rewrite; neither had the conditionality right), then verified against
the script and confirmed the ENTRYPOINT-override path is real rather than
hypothetical before writing this.

No add-on touched; references/ only. markdownlint clean.

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

* skill(hassio-addon-workflow): scope the stage-3 trap to the PID-1-entrypoint path

Flagged by both Codex and Copilot, reproduced against .templates/ha_entrypoint.sh
before accepting.

The previous commit's fix still overclaimed: it said ha_entrypoint.sh rewrites
every service's shebang, universally. It doesn't. The services.d/s6-rc.d rewrite
loop is gated on `if $PID1` (line 430) — it only runs when ha_entrypoint.sh
itself is the container's PID 1, i.e. when the add-on replaces the base
ENTRYPOINT ["/init"]. Under the normal /init path (the overwhelming majority of
add-ons), ha_entrypoint.sh runs as the stage-2 hook with PID1=false, never
touches service run files, s6's own stage 1 already created
container_environment before any cont-init script ran, and a service's shipped
with-contenv shebang reads it normally — the opposite of what the previous
wording implied. cont-init.d scripts are unconditional either way
(run_one_script has no $PID1 gate), so a cont-init script's own shebang was
never informative and that half stands.

Also fixed: the previous commit named wger as a live example of the
ENTRYPOINT override. Copilot caught that wger's override is written into its
Dockerfile but commented out — confirmed (`grep '^#ENTRYPOINT' wger/Dockerfile`).
Only ente and free_games_claimer currently have it active; wger dropped from
the list.

markdownlint clean; no add-on touched.

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

---------

Co-authored-by: BirdNET-Go Addon Builder <addon-builder@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Alexandre
2026-09-11 08:05:29 +02:00
committed by GitHub
parent fc07a2d1b4
commit d60f17685e

View File

@@ -70,10 +70,28 @@ service run scripts including `svc-xorg`, and Xvfb runs with `-vfbdevice /dev/dr
- `00-global_var.sh` is cont-init **00**. Any cont-init script numbered higher runs *after* the
injection, so it cannot change what a service will see through stage 2.
- LSIO's `svc-xorg` starts `#!/usr/bin/env bashio`, **not** `with-contenv`, so it never reads
stage 3 at all. Writing `container_environment` for it is a silent no-op — that shipped: the
file was written 6 seconds before Xvfb started, and Xvfb still came up at the base-image
default.
- **Whether a service's own shebang still decides stage 3 depends on whether `ha_entrypoint.sh`
runs as PID 1** — i.e. whether the add-on replaces the base `ENTRYPOINT ["/init"]` with one that
makes `ha_entrypoint.sh` itself the container's entrypoint (live today in `ente` and
`free_games_claimer`; `wger` has the override written into its Dockerfile but commented out, so
it is not currently one of these — check the Dockerfile, not this list). Under the normal
`/init` path this script runs as the stage-2 hook, `$PID1` is false, and it never touches
`services.d/*/run` or `s6-overlay/s6-rc.d/*/run` at all (`.templates/ha_entrypoint.sh:430`,
gated on `if $PID1`) — s6's own stage 1 already created `/run/s6/container_environment` before
any cont-init script ran, so a service's shipped `with-contenv` shebang reads it normally.
Only the `ENTRYPOINT`-override path breaks this, and it breaks it twice over: s6 stage 1 never
runs, so nothing ever creates the envdir; and because `ha_entrypoint.sh` is now PID 1, it
rewrites the first line of every service `run` file to whichever shebang its own
`candidate_shebangs` probe landed on. That probe's first candidate,
`/command/with-contenv bashio`, fails precisely because the envdir was never created, so it
falls through to `/usr/bin/env bashio` for every service — the real mechanism behind the shipped
`svc-xorg` failure (its envdir file was written 6 seconds before Xvfb started, and Xvfb still
came up at the base-image default). `cont-init.d` scripts are a separate case: `run_one_script`
rewrites their shebang unconditionally, with no `$PID1` gate, so a cont-init script's own
shebang is never informative either way. `ha_entrypoint.sh` dumps the environment itself to
compensate for the missing envdir, and the envdir writes in `00-global_var.sh` /
`01-config_yaml.sh` are `if [ -d ]` guarded, so they take effect only once something has created
that directory.
**Renaming an option to match a base-image env var moves validation out of your script and into
the schema.** `00-global_var.sh` exports empty strings (only objects/arrays/nulls are dropped),