fix(update): surface why a startup self-update failed (#545) [superseded by #543 — do not review standalone] - #548
Conversation
The startup auto-update hook runs the installer with capture_output=True and threw the transcript away, so a failed upgrade printed a bare "Auto-update failed; continuing with current version." The explicit `kbagent update` path has always surfaced result.stderr -- the startup path did not, which is why the Windows reports in #545 and #528 could say the venv was left broken but not what uv actually refused to do. _perform_update now returns an UpdateAttempt dataclass carrying the outcome plus the last actionable line of the installer transcript (stderr, falling back to stdout), and the failure banner prints it: Auto-update failed (error: Access is denied. (os error 5)); continuing with current version. Recover with: uv tool install --force ... The tail is compressed by summarize_failure_tail(), lifted out of VersionService._summarize_failure_tail (now a thin delegate) so both update paths report a failure identically. This is diagnostics only -- it does NOT fix the non-atomic in-place venv swap behind #528/#545. It makes the next report actionable.
|
Superseded — please read before reviewing. This PR was opened by the nightly issue-triage run for #545. After opening it I found #543, which was already in flight for #528 and is the same root cause: the reported broken install in #545 (0.76.2 → 0.77.0, #543 rewrites Recommendation: close this and, if the idea is wanted, fold it into #543 as a small follow-up. The residual value after #543 is narrow but real — on POSIX the inline install stays,
#545 should be closed as a duplicate of #528 once #543 ships. |
Scope, up front
This is diagnostics, not the fix. It does not change how the self-update mutates the tool environment, so it does not fix the corruption reported in #545 and #528. It removes the reason we cannot diagnose that corruption. Deliberately not marked
Fixes.What
The startup auto-update hook runs the installer with
capture_output=Trueand discarded the transcript, so a failed upgrade printed only:The explicit
kbagent updatepath has always surfacedresult.stderr(_update_kbagent→message/output). The startup path did not — which is why both Windows reports (#545, 0.76.2 → 0.77.0; #528, three separate upgrades) could say the venv was left broken but not whatuvactually refused to do._perform_update()now returns anUpdateAttemptfrozen dataclass:outcome(the existingUpdateOutcome) plusdetail, the last actionable line of the installer transcript —stderr, falling back tostdoutso the tail is never empty by accident.OSErrorand "no installer on PATH" get their own explanation.summarize_failure_tail()is lifted out ofVersionService._summarize_failure_tail(now a thin delegate) so both update paths compress a transcript identically.TIMEOUTandSUCCESScarry no detail, and a failure with nothing to report keeps the old wording — no empty parentheses.Why this matters for #545 / #528
Both reports describe the same failure mode: the update fails partway and leaves the uv tool venv stranded between versions (
ImportError: cannot import name 'rich_utils' from 'typer', thenModuleNotFoundError: No module named 'keboola_agent_cli'), recoverable only with the printeduv tool install --force --reinstallcommand. What we still do not know is whyuvexits non-zero in the first place — and without the installer's own last line, every new report reproduces that blind spot.The remaining architectural fix (a fresh-resolution or post-exit detached updater, as suggested in #528) changes what auto-update means on Windows — the current invocation would keep running the old version, with no re-exec and no "What's new" — so it is left as a maintainer decision rather than guessed at here.
How it was tested
TestPerformUpdateFailureDetail: stderr tail extraction, stdout fallback, missing-installer andOSErrorexplanations, and that a timeout carries no detail.TestFailureBannerText: the detail reaches stderr alongside the recovery command, and the banner stays clean when there is no detail.tests/test_version_service.py: the module-level helper and theVersionServicedelegate compress a transcript identically._perform_updateassertions updated for the new return type.make check: lint + format + changelog-check + full suite (4691 passed, 8 skipped).Refs #545, #528