workflows: install-deps step assumes sudo; fails on root-only images without sudo #10

Closed
opened 2026-06-24 17:54:14 +02:00 by quartermaster · 0 comments

Symptom

The reusable workflows (_release-prep.yml / _release-draft.yml / _release-publish.yml / _manifest-check.yml) have an "install deps (yq + jq + curl)" step that runs:

- name: install deps (yq + jq + curl)
  shell: bash
  run: |
    set -euo pipefail
    missing=()
    for tool in yq jq curl; do
      command -v "$tool" >/dev/null || missing+=("$tool")
    done
    if (( ${#missing[@]} > 0 )); then
      sudo apt-get update -qq
      sudo apt-get install -y --no-install-recommends "${missing[@]}"
    fi

On images that run as root but lack sudo (a common shape — minimal Debian-derived CI images), sudo apt-get install fails with sudo: command not found and the entire workflow tanks.

Empirical reproduction (caught 2026-06-24 during release-toolkit PR #6 CI)

The forgejo-ci-go:latest image (the runner image for runs_on: go) runs as root with NO sudo:

$ docker run --rm git.frankenbit.de/frankenbit/forgejo-ci-go:latest sh -c 'whoami; command -v sudo'
root
# (no output — sudo not installed)

When a workflow specifies runs_on: go and a tool is missing (yq, in our case), the install-deps step hits sudo: command not found and CI fails opaquely.

Patched the immediate breakage by baking yq into forgejo-ci-go (alcatraz-infra commit 7715b14), but the underlying workflow logic remains brittle for any consumer running a minimal root-only image.

Proposed fix (v0.2.1 patch)

Replace the install-deps step with sudo-aware logic:

- name: install deps (yq + jq + curl)
  shell: bash
  run: |
    set -euo pipefail
    missing=()
    for tool in yq jq curl; do
      command -v "$tool" >/dev/null || missing+=("$tool")
    done
    if (( ${#missing[@]} > 0 )); then
      if [[ $EUID -eq 0 ]]; then
        apt-get update -qq
        apt-get install -y --no-install-recommends "${missing[@]}"
      elif command -v sudo >/dev/null; then
        sudo apt-get update -qq
        sudo apt-get install -y --no-install-recommends "${missing[@]}"
      else
        echo "::error::install-deps: missing tool(s) [${missing[*]}] but not root and no sudo available"
        echo "::error::pre-bake these tools into your runner image, or run as root, or install sudo"
        exit 1
      fi
    fi
    # Verify yq is the Python wrapper (3.x) — Go-binary yq output for
    # `.foo // "x"` differs subtly and breaks config.sh.
    yq --version

This handles three shapes:

  1. Root user: skip sudo, install directly
  2. Non-root with sudo: use sudo (today's behavior)
  3. Non-root without sudo: fail loud with clear diagnostic (today: opaque "command not found")

Substrate-care discipline observation

This is sibling to release-toolkit#9 (silent fragment-drop): both are fail-loud-when-environment-isn't-what-tool-assumes disciplines. The toolkit shouldn't assume sudo any more than it should assume <id>.<kind>.md parse cleanly. Today (2026-06-24) had n=4 worked instances of feedback_binary_presence_before_behavior + feedback_substrate_claim_verification family across distinct surfaces — this PR-blocker is yet another instance.

Disposition lean

(A) v0.2.1 patch with the sudo-aware logic above + (B) bundle with #9 (loud warning on unrecognized fragment basenames) — both are fail-loud upgrades to the same install / parse-state-care discipline. Same release; reasonable to ship together.

Cross-tracker

  • alcatraz-infra commit 7715b14 baked yq into ci-go to unblock release-toolkit PR #6 CI immediately
  • release-toolkit#9 (fragment-parser silent drop) is the natural v0.2.1 sibling
  • release-toolkit PR #6 (slice 3) needs the alcatraz-infra unblock landed + a CI re-trigger to validate

— QM, 2026-06-24, surfaced during PR #6 CI investigation.

## Symptom The reusable workflows (`_release-prep.yml` / `_release-draft.yml` / `_release-publish.yml` / `_manifest-check.yml`) have an "install deps (yq + jq + curl)" step that runs: ```yaml - name: install deps (yq + jq + curl) shell: bash run: | set -euo pipefail missing=() for tool in yq jq curl; do command -v "$tool" >/dev/null || missing+=("$tool") done if (( ${#missing[@]} > 0 )); then sudo apt-get update -qq sudo apt-get install -y --no-install-recommends "${missing[@]}" fi ``` On images that run as **root but lack sudo** (a common shape — minimal Debian-derived CI images), `sudo apt-get install` fails with `sudo: command not found` and the entire workflow tanks. ## Empirical reproduction (caught 2026-06-24 during release-toolkit PR #6 CI) The `forgejo-ci-go:latest` image (the runner image for `runs_on: go`) runs as root with NO sudo: ```sh $ docker run --rm git.frankenbit.de/frankenbit/forgejo-ci-go:latest sh -c 'whoami; command -v sudo' root # (no output — sudo not installed) ``` When a workflow specifies `runs_on: go` and a tool is missing (yq, in our case), the install-deps step hits `sudo: command not found` and CI fails opaquely. Patched the immediate breakage by baking `yq` into `forgejo-ci-go` (alcatraz-infra commit 7715b14), but the underlying workflow logic remains brittle for any consumer running a minimal root-only image. ## Proposed fix (v0.2.1 patch) Replace the install-deps step with sudo-aware logic: ```yaml - name: install deps (yq + jq + curl) shell: bash run: | set -euo pipefail missing=() for tool in yq jq curl; do command -v "$tool" >/dev/null || missing+=("$tool") done if (( ${#missing[@]} > 0 )); then if [[ $EUID -eq 0 ]]; then apt-get update -qq apt-get install -y --no-install-recommends "${missing[@]}" elif command -v sudo >/dev/null; then sudo apt-get update -qq sudo apt-get install -y --no-install-recommends "${missing[@]}" else echo "::error::install-deps: missing tool(s) [${missing[*]}] but not root and no sudo available" echo "::error::pre-bake these tools into your runner image, or run as root, or install sudo" exit 1 fi fi # Verify yq is the Python wrapper (3.x) — Go-binary yq output for # `.foo // "x"` differs subtly and breaks config.sh. yq --version ``` This handles three shapes: 1. **Root user**: skip sudo, install directly 2. **Non-root with sudo**: use sudo (today's behavior) 3. **Non-root without sudo**: fail loud with clear diagnostic (today: opaque "command not found") ## Substrate-care discipline observation This is sibling to release-toolkit#9 (silent fragment-drop): both are **fail-loud-when-environment-isn't-what-tool-assumes** disciplines. The toolkit shouldn't assume sudo any more than it should assume `<id>.<kind>.md` parse cleanly. Today (2026-06-24) had n=4 worked instances of `feedback_binary_presence_before_behavior` + `feedback_substrate_claim_verification` family across distinct surfaces — this PR-blocker is yet another instance. ## Disposition lean (A) v0.2.1 patch with the sudo-aware logic above + (B) bundle with #9 (loud warning on unrecognized fragment basenames) — both are fail-loud upgrades to the same install / parse-state-care discipline. Same release; reasonable to ship together. ## Cross-tracker - **alcatraz-infra commit 7715b14** baked `yq` into ci-go to unblock release-toolkit PR #6 CI immediately - **release-toolkit#9** (fragment-parser silent drop) is the natural v0.2.1 sibling - **release-toolkit PR #6** (slice 3) needs the alcatraz-infra unblock landed + a CI re-trigger to validate — QM, 2026-06-24, surfaced during PR #6 CI investigation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#10
No description provided.