fix(host-agent): a large update records its package count (#6794)
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m38s
CI / integration (push) Successful in 2m1s
CI / publish (push) Successful in 20s

The root helper counted updated packages from the last 2,000 characters
of the package manager's output. Every manager prints its count (apt's
"N upgraded, N newly installed", dnf's "Upgrade N Packages", zypper's
"N packages to upgrade", pacman's "Packages (N)") before the long log of
what it then does, so a large update pushed the line out of the tail
and the job recorded no count. Docker03's 2026-11-01 job applied 17
updates and reported none; Docker02's single package was counted.

The update steps now read their whole output (run(keep=None)), and only
the failure text and the job's detail are cut short. Agent 1.26.1, so
hosts show the helper as an update to take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 23:03:07 -05:00
co-authored by Claude Opus 5.5
parent c3072312a4
commit 12a5f83c67
3 changed files with 23 additions and 7 deletions
+7 -5
View File
@@ -101,10 +101,10 @@ def valid(req) -> bool:
def run(cmd: list, deadline: int = COMMAND_DEADLINE_SECONDS, env: dict | None = None,
ok_codes: tuple = (0,), keep: int = OUTPUT_KEPT_CHARS):
ok_codes: tuple = (0,), keep: int | None = OUTPUT_KEPT_CHARS):
"""(ok, output tail) of a command, bounded by `deadline`. `ok_codes` are
the exit codes that mean it worked (zypper says more than 0); `keep` is
how much of the output's end is kept."""
how much of the output's end is kept, None for all of it."""
try:
done = subprocess.run(cmd, capture_output=True, text=True, timeout=deadline,
env=dict(os.environ, LANG="C", LC_ALL="C", **(env or {})))
@@ -113,7 +113,7 @@ def run(cmd: list, deadline: int = COMMAND_DEADLINE_SECONDS, env: dict | None =
except OSError as exc:
return False, str(exc)
out = (done.stdout or "") + (done.stderr or "")
return done.returncode in ok_codes, out[-keep:]
return done.returncode in ok_codes, out if keep is None else out[-keep:]
REBOOT_MARKER = "/var/run/reboot-required" # Debian and Ubuntu
@@ -226,9 +226,11 @@ def do_update() -> dict:
"(apt, dnf, yum, zypper, pacman) on this host"}
out = ""
for label, cmd, deadline, env, ok_codes in update_steps(manager):
ok, out = run(cmd, deadline=deadline, env=env, ok_codes=ok_codes)
# The whole output: every manager prints its count before the long
# log of what it then does, so a tail loses it on a large update.
ok, out = run(cmd, deadline=deadline, env=env, ok_codes=ok_codes, keep=None)
if not ok:
return {"ok": False, "detail": f"{label} failed: " + out}
return {"ok": False, "detail": f"{label} failed: " + out[-OUTPUT_KEPT_CHARS:]}
changed = upgraded_count(out)
if manager == "apt":
run(["apt-get", "-y", "autoremove"], env={"DEBIAN_FRONTEND": "noninteractive"})