Skip to content

setup services exit 127 when their hooks directory exists but is empty (ublue-system-setup / ublue-user-setup / ublue-privileged-setup) #1062

Description

@kubestellar-hive

Summary

All three first-boot setup dispatchers fail with exit 127 when their hooks directory exists but contains no files.

system_files/shared/usr/bin/ublue-system-setup (and the identical loop in ublue-user-setup / ublue-privileged-setup):

if [ -d "${SYSTEM_HOOKS_DIRECTORY}" ] ; then
	for script in "${SYSTEM_HOOKS_DIRECTORY}"/* ; do
		bash "$script"
	done
fi

nullglob is not set, so an empty directory leaves the glob unexpanded and the loop runs bash "/path/to/hooks/*".

Reproduction (against main)

$ W=$(mktemp -d); mkdir -p "$W/hooks"
$ echo "{\"system-hooks-directory\": \"$W/hooks\"}" > "$W/setup.json"
$ SETUP_CONFIG_FILE="$W/setup.json" bash system_files/shared/usr/bin/ublue-system-setup
bash: /tmp/tmp.XXXX/hooks/*: No such file or directory
$ echo $?
127

Impact

ublue-system-setup.service and ublue-user-setup.service are Type=oneshot first-boot units. A downstream variant or fork that ships an empty system-setup.hooks.d / user-setup.hooks.d / privileged-setup.hooks.d — or one where the last hook is removed — gets a red first-boot setup unit rather than a clean no-op. privileged-setup.hooks.d is not shipped by common at all, so it is entirely downstream-provided and most exposed.

Existing coverage only exercises the missing directory case (exits cleanly when hooks directory missing), never the empty one, in both tests/test_setup_scripts.bats and tests/test_privileged_setup.bats.

Recommendation

Set shopt -s nullglob around the loop (or guard each iteration with [ -e "$script" ] || continue) and add empty-directory regression tests for all three services.

Note: the fix currently has to be applied three times because the dispatcher is duplicated across the three scripts. If the deduplication refactor lands first, this becomes a one-line change in hookrunner.sh. This issue is filed as the behavioural defect only — the refactor deliberately preserves current behaviour.


Filed by architect agent (ACMM L5 — hold-gated mode)

🐝 Hive Agent: architect | Instance: hosted-projectbluefin-knuckle-gjvq | SHA: unknown

— hive: agent=architect backend=copilot model=claude-opus-5

Metadata

Metadata

Assignees

No one assigned

    Labels

    1-triageNew work awaiting human triage.agent/architectApproved by a Hive merger/owner for auto-merge on green CIhive/hosted-projectbluefin-knuckle-gjvqApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions