Skip to content

recheck: stream live output (verbose real command output or narration) - #3

Merged
sinwe merged 7 commits into
mainfrom
recheck-live-streaming
Aug 23, 2026
Merged

sinwe merged 7 commits into
mainfrom
recheck-live-streaming

Conversation

@sinwe

@sinwe sinwe commented Aug 21, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • "Force recheck" previously did nothing visible in the live-output pane -- the agent's ActionRecheck handling was fire-and-forget and never streamed anything. It now runs synchronously and streams to the same live console Apply/Upgrade already use, regardless of whether a companion is installed on that host.
  • A per-row "verbose" checkbox chooses between real shell command output (apt-get/apt-check/winget/powershell, same fidelity as Upgrade) and short synthetic progress narration (default).
  • Refreshing the browser mid-recheck now replays whatever was already streamed before continuing live (OutputHub now buffers each in-flight action's lines) -- scoped to in-flight only, nothing persisted once an action ends.
  • Deleted internal/aggregator/adminbus.go, an unused, superseded-by-OutputHub pub/sub left over from an earlier attempt at this same problem.
  • Promoted internal/companion's private line-tee writer into a shared internal/linetee package so internal/checker's backends can reuse it without an import cycle, and exported OutputSink.Push so the agent binary can push into a sink it owns directly.

Test plan

  • go build ./... / go vet ./... on Linux and GOOS=windows locally -- clean.
  • go test ./... locally -- all green except two pre-existing, unrelated Unix-socket-path failures specific to this sandbox (internal/companion's TestFetchIdentityWithRetry*, internal/companiontoken's TestListenRemovesStaleSocket) -- not touched by this change.
  • New fake-script-backed tests (reusing the existing writeFakeAptGet pattern) verify the real-output tee actually receives a fake command's lines, with and without a sink attached, in internal/checker/ubuntu, internal/checker/debian, and internal/aptutil.
  • New OutputHub tests cover backlog replay on subscribe, backlog cleared on End, and backlog reset on a new Begin.
  • Not verified here, needs a real host: actually clicking "Force recheck" (verbose and non-verbose) against a real agent, confirming a mid-recheck browser refresh replays correctly, and confirming an agent-only (no companion) host behaves identically to a companion-present host through the real admin UI. Recommend a manual pass on a real Linux host before merging.
  • CI (ubuntu-latest + windows-latest matrix) -- pending on this PR.

sinwe added 7 commits August 21, 2026 12:14
Force recheck previously did nothing visible: the agent's ActionRecheck
handling was fire-and-forget (queue a signal, report success
unconditionally) and never opened the same output stream Apply/Upgrade
already use. It now runs synchronously and streams to the same live
console, whether or not a companion is installed on that host -- the
agent always holds its own dedicated stream for non-companion actions
like recheck, so one fix covers both cases.

A per-row "verbose" checkbox controls what streams: checked taps the
real apt-get/apt-check/winget/powershell output (via a new optional
checker.WithLineSink context hook, wired through aptutil and each
platform backend with zero behavior change when absent); unchecked
(default) emits short synthetic progress narration instead.

OutputHub now buffers each in-flight action's lines so a browser
reconnecting mid-recheck (e.g. a page refresh) replays what it missed
before continuing live -- scoped to in-flight only, nothing persisted
once an action ends.

Also: promoted internal/companion's private lineTee into a shared
internal/linetee package (checker's backends need the same line-splitting
tee, and companion already imports checker, so checker can't import
companion back); exported OutputSink.Push so the agent binary can push
into a sink it owns directly. Deleted internal/aggregator/adminbus.go,
an unused, superseded-by-OutputHub pub/sub left over from an earlier
attempt at this same problem.
They fake apt-get as a #!/bin/sh script on PATH, which windows-latest's
exec.LookPath can't run (no shebang support, PATHEXT-based lookup
expects a known executable extension) -- confirmed by CI failing on
exactly this. apt-get itself is Linux-only in practice anyway, matching
the same //go:build !windows already on the equivalent ubuntu/debian
checker tests added alongside these.
…ver set)

Confirmed on real Ubuntu, Windows, and WSL hosts: Force recheck opened
the live-output pane but never showed anything, verbose or not, on any
platform. Root cause: CompanionHub.Push's agentStreams branch (the one
that routes recheck, since it doesn't require a companion) pushed the
action but never recorded it in any in-flight map. handleCompanionOutput
authorizes every output-stream POST against that same tracking
(previously just `pending`), so the agent's own StreamOutput call for a
recheck was rejected with 409 on its very first request -- silently,
since the failure only ever logged on the agent side, never surfaced to
the browser.

Added a separate agentPending map (recheck must never be blocked by, or
clobber the tracking of, an already in-flight companion action on the
same host -- Push deliberately skips the ErrActionInFlight check for it)
and a new IsPending(agentID, actionID) that checks both maps, used by
handleCompanionOutput instead of comparing against the single-value
Pending() getter (which stays as-is for its own separate "what to resume
watching after a page reload" purpose).
Confirmed on a real Windows host: applying a winget-sourced package via
the companion (always running as a Windows Service, so stdin is never a
real console) failed with "ERROR: Input redirection is not supported,
exiting the process immediately." -- a winget error that
--accept-package-agreements/--accept-source-agreements alone don't
prevent. The detection side's own winget invocation
(internal/checker/windows/packages.go) already passes
--disable-interactivity and has no such problem; the apply side's three
winget commands (Packages, Upgrade, and FullUpgrade via Upgrade) were
missing it.
The companion binary reported the self-update-of-itself action's result
*before* running Apply at all (Linux), specifically to guarantee an
outcome got recorded even though install.sh's own systemctl restart
kills this very process partway through. That report ends the action's
output stream (OutputHub.End fires on the first /companion/result, and
Publish becomes a no-op afterward for that action ID) -- so the live
pane always closed itself off before install.sh had produced a single
line, on every self-update-of-companion, regardless of how long the
process actually survived first.

Apply now always runs first, synchronously, exactly like every other
action -- its real output streams normally for however long the process
survives. The existing (and separately confirmed-live) rule that a
failure coinciding with ctx already being canceled is spurious (the
process is mid-restart, not actually broken) now substitutes the
optimistic message only in that specific case, after the fact, instead
of assuming it unconditionally upfront. This also let the Windows-only
branch collapse away entirely: Apply already returns a real result
there (no restart of this process happens on that platform at all), so
both platforms now share one code path.
README's intro said the agent "never applies" updates -- true of the
agent process itself, but no longer true of the project as a whole now
that the companion can apply on trigger. Rewrote the intro to say that
plainly, and to state clearly that the agent (update-detector) and the
aggregator (update-aggregator) are two separate Docker images, not one --
the old "ships as a single Docker image" line was ambiguous now that the
paragraph covers all three components.

Also: winget is not supported by this project (confirmed) -- it never
runs under install.bat's default LocalSystem service account in the
first place. Reframed every winget mention in docs/reference.md's
Platform limitations from "optional/supplementary signal" to "not
supported, here's why," and updated both docs to reflect that detection,
install.bat, Force recheck streaming, and companion self-update have now
been confirmed against a real Windows host this session.
Windows companion self-update is two actions, not one: the companion
stages a new binary (ActionSelfUpdate), then the agent stops the
service/swaps the binary/restarts it (ActionCompleteCompanionSwap,
auto-pushed on a staged success). CompleteCompanionSwap already called
emitFromContext throughout (stopping/swapping/starting narration) --
ready to stream -- but its caller in cmd/update-detector/main.go never
attached a sink to its context at all, so there was nothing to stream to
regardless. Wired it up with the same sink/StreamOutput pattern already
used for recheck and apply.

That alone wasn't enough, though: the browser's live-output pane closes
itself the moment it sees "done" for the first (staged) action, before
the swap action -- now streaming correctly -- even starts, so the second
phase would still never be seen without a manual page reload. OutputHub's
"done" event now carries whether the underlying result was staged, and
the admin page's JS re-subscribes to keep watching instead of switching
to version-polling when it sees that.
@sinwe
sinwe merged commit 96ed748 into main Aug 23, 2026
7 of 8 checks passed
@sinwe
sinwe deleted the recheck-live-streaming branch August 23, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant