Skip to content

fix(installer): honest section numbering, and stop leaking subprocess output#152

Merged
fullstackjam merged 2 commits into
mainfrom
fix/install-log-structure
Jul 17, 2026
Merged

fix(installer): honest section numbering, and stop leaking subprocess output#152
fullstackjam merged 2 commits into
mainfrom
fix/install-log-structure

Conversation

@fullstackjam

Copy link
Copy Markdown
Member

What does this PR do?

Makes the apply log readable: section numbers are derived from the plan instead of hardcoded, each failure is reported once, and subprocesses stop narrating themselves into the middle of a section.

Also fixes the v0.66.1 release, which failed its vm-e2e gate — see below.

Why?

Reported from a real run. The log read:

=== Step 1: Git Configuration ===
=== Step 4: Installation ===          ← 2, 3?
=== Shell Configuration ===           ← no number at all
=== Step 6: Dotfiles ===              ← 5?
=== Step 7: macOS Preferences ===

The numbers lived in each step's own header string; whether a step ran was decided separately by its own guard. Two sets of logic, no way to agree — and every plan has conditional steps, so the numbers could only ever be wrong.

ApplyContext now derives the section list from the plan and prints [i/n]. Steps keep their guards and lose their headers, so the numbering is dense by construction: --packages-only prints [1/1], a plan without dotfiles leaves no hole, and npm gets a real section instead of none.

Same log, same run, also fixed:

  • errors printed twiceapplyPackages/applyNpm announced their own failure and the caller announced it again. ApplyContext is now the single reporter.
  • ✓ ✓ Already configuredui.Success already prefixes the tick.
  • subprocess leakageHEAD is now at 5a1dc8e feat: add Makefile…, Receiving objects: 100% (40/40), stow -v --target=…. Captured now, and attached to the error, which reads better than the raw stream did:
    ✗ Dotfiles failed: link dotfiles: make install: exit status 2:
      /bin/sh: stow: command not found
    
    system.RunCommandInDirSilent is the captured counterpart of RunCommandInDir, added because subprocesses go through internal/system by convention.

These warts predate the wizard. They were buried in an alt-screen log that dropped Info/Header lines; moving the apply back to the scrollback (#150) made this output the thing you actually read.

The v0.66.1 release failure

release.yml for v0.66.1 failed at TestVM_Interactive_InstallScript/reinstall_answer_no, so no binaries were published — the installer fix (#151) went live on openboot.dev but never reached the tap.

The test is worth reading carefully. It drove the Reinstall? (y/N) prompt with expect, sent n, and asserted "should keep existing installation". It passed, and it verified nothing. Under curl … | bash the read never saw expect's keystroke — it consumed the script's own bytes, failed to match ^[Yy]$, and took the No branch. Which is exactly what the test asserted. The bug and the assertion agreed, so the suite stayed green while every returning user was pinned to their first-installed version.

#151 removed the prompt, so the test broke — after merge, not on the PR. That's the second thing worth noting: the test curls the live https://openboot.dev/install.sh, so it tests production, not the PR. #151 was green because openboot.dev still served the old script at the time; the deploy on merge is what broke the release job.

Rewritten to pin the new contract: an existing install is updated without prompting, and the resolved version is printed. It drives bash < /dev/null so the no-keyboard-on-stdin hazard is reproduced rather than papered over.

Testing

  • go vet ./... passes
  • Relevant tests added or updated — internal/installer/steps_test.go pins dense numbering, skip-what-won't-run, and order; vm_interactive_test.go rewritten for the new installer contract
  • Tested locally — full L1 green (25 packages incl. archtest); golangci-lint v2.11.4 clean; driven end-to-end on a real pty, output verified section by section
  • archtest baseline refreshed — line shifts only, same six pre-existing exemptions, no new violations

Notes for reviewer

Worth a follow-up decision, recorded in HARNESS.md rather than fixed here: the e2e installer test hitting the live URL means it can only fail after a merge has already shipped. Pointing it at the PR's own scripts/install.sh would make it a real gate. Related: curl-bash-smoke is still gated if: github.event_name != 'pull_request' and shows as skipping on every PR — including #151, which changed nothing but that script.

…bprocess output

The apply log read as:

    === Step 1: Git Configuration ===
    === Step 4: Installation ===
    === Shell Configuration ===
    === Step 6: Dotfiles ===
    === Step 7: macOS Preferences ===

1, 4, 6, 7, with two unnumbered sections wedged in. The numbers were
written into each step's own header string, while whether a step ran was
decided separately by its own guard — two sets of logic with no way to
agree. Any conditional step made the numbering wrong, and every plan has
conditional steps.

ApplyContext now derives the section list from the plan and prints
[i/n] itself; the steps keep their guards and lose their headers. The
numbering is dense by construction and matches what actually runs:
--packages-only prints [1/1], a plan with no dotfiles doesn't leave a
hole where dotfiles would have been, and npm — which never had a number
at all — gets a real section.

Also visible in the same log, now fixed:

- errors printed twice. applyPackages and applyNpm announced their own
  failure and the caller announced it again, so one failure read as two.
  ApplyContext is the single reporter; steps return.
- "✓ ✓ Already configured" — ui.Success already prefixes the tick.
- git narrating itself mid-section ("HEAD is now at 5a1dc8e feat: add
  Makefile…", "Receiving objects: 100% (40/40)"), and the dotfiles
  Makefile echoing its own recipe ("stow -v --target=…"). Both are
  captured now and attached to the error instead, so a failure is still
  diagnosable — the message it produces is strictly better than the raw
  stream was:

    ✗ Dotfiles failed: link dotfiles: make install: exit status 2:
      /bin/sh: stow: command not found

  system.RunCommandInDirSilent is the captured counterpart of
  RunCommandInDir, added because the convention is that subprocesses go
  through internal/system.

These warts predate the wizard, but they were buried in an alt-screen log
that dropped Info/Header lines. Moving the apply back to the scrollback
made this output the thing you actually read, which is what surfaced them.
@github-actions github-actions Bot added installer Package installation logic tests Tests only labels Jul 17, 2026
The rewritten test used `curl … | bash < /dev/null` to make "there is no
keyboard on stdin" explicit. Under bash that redirect replaces the script
itself — bash reads its commands from stdin, so it got /dev/null, ran
nothing, and every assertion saw empty output. (Under zsh the same line
runs the script, which is how it looked fine locally.)

Plain `curl … | bash` already is the no-keyboard case; that's the whole
point of the bug being fixed. Use it, with -s -- --help so the exec'd
`openboot install` prints usage instead of installing for real.
@fullstackjam
fullstackjam merged commit 881757a into main Jul 17, 2026
14 checks passed
@fullstackjam
fullstackjam deleted the fix/install-log-structure branch July 17, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

installer Package installation logic tests Tests only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant