You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[quality] regression-risk: version-script stamps the version before the hook body runs, so a failed first-boot hook never retries #1137
version-script() in system_files/shared/usr/lib/ublue/setup-services/libsetup.sh writes the new version into $SETUP_CHECKER_FILEas part of the gate check, before the calling hook has done any of its work:
if jq ".version.${TYPE_OF_SERVICE}.\"${TARGET_VERSIONING_NAME}\" = \"${VERSION}\"" "${SETUP_CHECKER_FILE}" > "${tmp}"; then
mv "${tmp}" "${SETUP_CHECKER_FILE}"
Every hook uses it as version-script <name> <type> <n> || exit 0 and then does the real work afterwards. So if the body fails — the machine is offline, a unit is masked, a package is not yet present — the version has already been recorded and the gate makes every subsequent run exit 0 immediately. The setup step is permanently skipped, silently, with no error surfaced after the first boot.
Demonstrated on the dynamic-wallpaper hook: make systemctl --user enable --now bluefin-dynamic-wallpaper.timer fail once and the hook exits non-zero with .version.user."dynamic-wallpaper" == "1" already on disk; the next run enables nothing and returns 0. This is asserted today as a characterization test in tests/test_dynamic_wallpaper_hook.bats ("a failed run still burns the version and never retries") so the behaviour is at least visible.
This is not specific to that hook — it applies to every caller of version-script, including the hardware, theming, oem-brew and privileged-setup paths.
Recommendation
Split the gate from the commit so the version is only recorded on success. Either:
add a companion version-script-commit <name> <type> <n> that hooks call at the end, leaving version-script a pure read, or
have hookrunner.sh record the version for a hook only when the hook exits 0.
Whichever shape is chosen, update the characterization test in tests/test_dynamic_wallpaper_hook.bats to assert the retry instead of the burn, and add a tests/test_libsetup.bats case for "failed body does not record the version".
Priority
Impact: medium (silent permanent skip of a first-boot setup step)
Covered by PR #1196 (fix/version-script-retry), now open against main.
The fix splits the gate from the commit:
version-script becomes a pure read gate (reports whether the hook has run at
this version, records nothing).
new version-script-commit <name> <type> <n> is called by each hook at the end
of its body, so the version is only recorded on success. A failing body never
reaches the commit and retries next boot instead of being permanently skipped.
shared flock + JSON-init logic moved into _setup_versioning_file().
Applied to the dynamic-wallpaper, theming, framework, asus and oem-brew hooks.
The dynamic-wallpaper characterization test is flipped to assert the retry, and a test_libsetup.bats case asserts that a failed body does not record the version.
All existing libsetup, dynamic_wallpaper_hook, theming_hook and setup_scripts bats tests plus the pytest suite pass.
Activity
test(dynamic-wallpaper): BATS coverage for the 20-dynamic-wallpaper.s…
kylerankin commentedon Sep 23, 2026
Covered by PR #1196 (
fix/version-script-retry), now open againstmain.The fix splits the gate from the commit:
version-scriptbecomes a pure read gate (reports whether the hook has run atthis version, records nothing).
version-script-commit <name> <type> <n>is called by each hook at the endof its body, so the version is only recorded on success. A failing body never
reaches the commit and retries next boot instead of being permanently skipped.
_setup_versioning_file().Applied to the dynamic-wallpaper, theming, framework, asus and oem-brew hooks.
The dynamic-wallpaper characterization test is flipped to assert the retry, and a
test_libsetup.batscase asserts that a failed body does not record the version.All existing
libsetup,dynamic_wallpaper_hook,theming_hookandsetup_scriptsbats tests plus the pytest suite pass.— hive: backend=pi model=lemonade/Ornith-1.5-35B-A3B-GGUF-Q6_K
🐝 Hive Agent:
contributor| SHA:52cceaafix(libsetup): hold lock across version commit; make framework/asus a…
fix(libsetup): keep version-script check-and-record; add version-scri…
fix: record setup version only after the hook body succeeds
fix(libsetup): hold lock across version commit; make framework/asus a…
fix(libsetup): keep version-script check-and-record; add version-scri…
fix(libsetup): migrate damask hook, abort asus before commit, log skips
feat: streamline common issue intake and lifecycle (#1367)
15 remaining items
fix(libsetup): keep version-script check-and-record; add version-scri…
fix(libsetup): migrate damask hook, abort asus before commit, log skips
fix: record setup version only after the hook body succeeds
fix(libsetup): hold lock across version commit; make framework/asus a…
fix(libsetup): keep version-script check-and-record; add version-scri…
fix(libsetup): migrate damask hook, abort asus before commit, log skips
fix: record setup version only after the hook body succeeds
fix(libsetup): hold lock across version commit; make framework/asus a…
fix(libsetup): keep version-script check-and-record; add version-scri…
fix(libsetup): migrate damask hook, abort asus before commit, log skips
fix: record setup version only after the hook body succeeds (projectb…