Skip to content

fix(update): stream update child output to the live log (PYTHONUNBUFFERED) - #64576

Merged
nousbot-eng merged 1 commit into
NousResearch:mainfrom
joelbrilliant:fix/desktop-update-stream-output
Jul 17, 2026
Merged

fix(update): stream update child output to the live log (PYTHONUNBUFFERED)#64576
nousbot-eng merged 1 commit into
NousResearch:mainfrom
joelbrilliant:fix/desktop-update-stream-output

Conversation

@joelbrilliant

Copy link
Copy Markdown
Contributor

Problem

When the desktop app updates itself (Tauri Hermes-Setup --update hand-off, or the in-app POSIX path), the spawned hermes update is a Python CLI writing to a pipe, so CPython block-buffers its stdout. Nothing reaches the live log until 8 KB accumulate or the process exits.

The worst case is the pre-update backup (updates.pre_update_backup: true): it can zip multi-GB archives for minutes while the updater UI still shows the previous line. On my install the updater sat on [update] waiting for Hermes to exit… for over 3 minutes while a 4.2 GB pre-update-*.zip was being written:

# ~/.hermes/logs/bootstrap-installer.log (frozen from 19:23:31Z)
2026-07-14T19:23:30Z INFO Hermes Setup starting mode=Update
2026-07-14T19:23:31Z INFO bootstrap.log: [update] waiting for Hermes to exit… stage=update

# meanwhile:
-rw-r--r-- 4185664739 Jul 15 05:26 ~/.hermes/backups/pre-update-2026-07-15-052331.zip

A user reads that silence as a hang and cancels — and because the child is not killed with the updater, the orphaned hermes update keeps mutating the install headless. The same silent-stage behaviour applies to the desktop's in-app POSIX update path.

Fix

Set PYTHONUNBUFFERED=1 at both spawn sites so the child streams line by line:

  • update_child_env() in apps/bootstrap-installer/src-tauri/src/update.rs (covers the hermes update and hermes desktop --build-only stages of the Tauri hand-off)
  • the env handed to runStreamedUpdate in applyUpdatesPosixInApp (apps/desktop/electron/main.ts)

With this, the "Creating pre-update backup…" line (and everything after) appears in the live log the moment it is printed, so a long backup reads as progress instead of a freeze.

Also in this PR

cargo test for the installer never passed on macOS: lock_probe_paths_include_desktop_app_payload asserts a probe ends with resources/app.asar, but the macOS payload lives under Contents/Resources/ and Path::ends_with is case-sensitive. The assertion now accepts both layouts.

Validation

  • cargo test --lib (apps/bootstrap-installer/src-tauri): 28 passed, 0 failed on macOS arm64, including a new unit test asserting PYTHONUNBUFFERED=1 is present in the update child env
  • npx tsc -p apps/desktop --noEmit: clean
  • Reproduced the buffering silence on a real install (macOS 15 / Darwin 25.5.0) before the change; logs above

🤖 Generated with Claude Code

@teknium1

Copy link
Copy Markdown
Contributor

Verified the reported buffering path on current main. apps/bootstrap-installer/src-tauri/src/update.rs:642-650 pipes the child streams and passes update_child_env() to both update and rebuild launches; that environment currently lacks PYTHONUNBUFFERED at :730-742. The POSIX in-app path has the same shape: apps/desktop/electron/main.ts:2649-2655 uses pipe stdio and :2718-2721 builds the shared child environment used at :2767-2771 and :2789.

The proposed setting matches the established gateway update-streaming behavior in gateway/slash_commands.py:4622-4631 and :4654-4656. The macOS test adjustment also matches the production macOS payload suffix in apps/bootstrap-installer/src-tauri/src/update.rs:554-558.

No blocking issues found. The current-main hunk contexts remain unchanged, so this appears mechanically salvageable.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 16, 2026
…ERED)

hermes update is a Python CLI writing to a pipe when the Tauri updater or
the desktop's in-app POSIX path spawns it, so CPython block-buffers stdout.
Long quiet steps stream nothing to the progress UI. Worst case is the
pre-update backup (updates.pre_update_backup: true): it can zip multi-GB
archives for minutes while the updater still shows the previous line
('waiting for Hermes to exit...'). Users read that as a hang, cancel a
healthy update, and the orphaned child keeps mutating the install.

Set PYTHONUNBUFFERED=1 in both spawn sites (update_child_env in the Tauri
updater, applyUpdatesPosixInApp in the desktop) so output streams line by
line.

Also make the lock-probe unit test pass on macOS: the packaged payload
lives under Contents/Resources there, and Path::ends_with is
case-sensitive, so the lowercase resources/app.asar assertion only ever
matched the Windows/Linux layouts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joelbrilliant
joelbrilliant force-pushed the fix/desktop-update-stream-output branch from 03d338a to 86eba6f Compare July 16, 2026 03:49
@joelbrilliant

Copy link
Copy Markdown
Contributor Author

Thanks for the verification. Rebased onto current main (clean, hunk contexts unchanged as you noted) and re-ran the installer suite on the rebased branch: 28 passed including the new env unit test. Good pointer on gateway/slash_commands.py already doing this for its own update streaming — same rationale, now consistent across all three update surfaces.

@nousbot-eng
nousbot-eng enabled auto-merge July 17, 2026 20:16
@nousbot-eng
nousbot-eng merged commit 7f76fc0 into NousResearch:main Jul 17, 2026
23 checks passed
@teknium1 teknium1 added area/install-update Installer, updater, packaging, wheels, doctor area/streaming Streaming responses: gateway delivery, provider wire labels Jul 19, 2026
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…update-stream-output

fix(update): stream update child output to the live log (PYTHONUNBUFFERED)
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…update-stream-output

fix(update): stream update child output to the live log (PYTHONUNBUFFERED)
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…update-stream-output

fix(update): stream update child output to the live log (PYTHONUNBUFFERED)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor area/streaming Streaming responses: gateway delivery, provider wire comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants