Skip to content

feat(shell): silent startup auto-update (default on) with config + env opt-outs - #130

Merged
elkaix merged 15 commits into
mainfrom
feat/silent-auto-update
Jun 13, 2026
Merged

feat(shell): silent startup auto-update (default on) with config + env opt-outs#130
elkaix merged 15 commits into
mainfrom
feat/silent-auto-update

Conversation

@elkaix

@elkaix elkaix commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces the blocking pre-start update prompt with a silent, non-blocking background auto-update at startup (on by default). When a newer installable release exists, Pythinker installs it in a background task and surfaces a single one-line Updated X → Y. Restart Pythinker to apply. notice — the running session keeps going on the old version until you restart. Per-channel install behavior is unchanged (byte-for-byte).

What changed

  • Config + env: new auto_update: bool field (default true) and PYTHINKER_AUTO_UPDATE env mapping. The hard kill-switch PYTHINKER_CLI_NO_AUTO_UPDATE still takes precedence.
  • Resolver: auto_update_enabled(config) encodes precedence — env kill-switch → config.auto_update is False → source checkout → enabled.
  • Startup wiring: the blocking pre-start prompt is gone; _schedule_startup_update_task() dispatches the silent installer when enabled, the existing toast-only path otherwise, and schedules nothing when the kill-switch is set. (prompt_pre_start_update_job is retained for future re-wiring.)
  • Silent installer: _silent_auto_update() runs the existing run_update_job(print_output=False) orchestrator and toasts the result. Smoke-check failures surface "verification failed", never "Restart to apply".
  • Robustness: SystemExit (Windows native/pip installer hand-off) is re-raised at the job level and swallowed with logging only at the background-task _cleanup boundary, so it never crashes the shell.
  • DRY: the managed-channel notice text (format_managed_channel_notice) and the smoke-fail sentinel (SMOKE_CHECK_FAILED_PREFIX) each have a single source of truth.
  • Docs: auto_update config field, PYTHINKER_AUTO_UPDATE env var (+ per-channel behavior), and an updated kill-switch description; ## Unreleased CHANGELOG entry.

No install/execution logic below run_update_job changed — per-channel upgrade behavior stays owned by the existing update layer.

Per-channel behavior

  • Windows (native installer / pip): process exits so the installer can replace the binary.
  • Managed channels (Docker/Nix/Scoop/WinGet): no binary swap — a channel-native upgrade hint is shown instead.
  • Source checkouts: never auto-update.

Testing

  • make check-pythinker-code (ruff + format + pyright) — green, 0 errors
  • Focused surface (config + update + shell + new silent-update tests) — 206 passed
  • Broad suite (tests + tests_e2e) — 5619 passed, 11 skipped, 1 xfailed. The single local failure (test_shell_cancel_running_command_kills_process_and_recovers) is a pre-existing, load-sensitive PTY/ESC-flush flake on this machine (green in CI); it lives in shell-cancel code this branch does not touch.

New tests cover: resolver precedence (6-way), managed-notice helper, print_output result-invariance, the silent flow (success → restart notice, smoke-fail → verification-failed, FAILED → silent, managed → channel hint, throttle), the three dispatch branches, and SystemExit survival in background-task cleanup.

Summary by CodeRabbit

Release Notes

  • New Features
    • Background auto-updates now run silently at startup (enabled by default)
    • Control auto-updates using PYTHINKER_AUTO_UPDATE environment variable or configuration setting
    • Non-blocking update notifications alert you when a restart is needed
    • Managed-channel installations receive channel-specific upgrade guidance

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@elkaix, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 6 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0f56fef3-c5c6-4e46-8271-50215110caf0

📥 Commits

Reviewing files that changed from the base of the PR and between f301044 and d94fa1b.

⛔ Files ignored due to path filters (2)
  • docs/en/configuration/config-files.md is excluded by !docs/**
  • docs/en/configuration/env-vars.md is excluded by !docs/**
📒 Files selected for processing (8)
  • CHANGELOG.md
  • src/pythinker_code/config.py
  • src/pythinker_code/ui/shell/__init__.py
  • src/pythinker_code/ui/shell/update.py
  • src/pythinker_code/ui/shell/update_orchestrator.py
  • tests/core/test_config.py
  • tests/ui_and_conv/test_shell_update.py
  • tests/ui_and_conv/test_silent_auto_update.py
📝 Walkthrough

Walkthrough

This PR adds silent background auto-updates at Shell startup, replacing a blocking pre-start prompt. It introduces a configuration field auto_update (default True), helper functions for update eligibility checks and managed-channel notice formatting, and comprehensive test coverage including success, smoke-check failure, throttling, and dispatch scenarios.

Changes

Silent Auto-Update Implementation

Layer / File(s) Summary
Configuration for Auto-Update
src/pythinker_code/config.py, tests/core/test_config.py
Config model gains auto_update: bool (default True); ENV_FIELD_MAP recognizes PYTHINKER_AUTO_UPDATE. Tests verify env override, default value, and explicit False preservation.
Update Decision and Result Handling
src/pythinker_code/ui/shell/update.py, src/pythinker_code/ui/shell/update_orchestrator.py, tests/ui_and_conv/test_shell_update.py
auto_update_enabled() computes eligibility using env kill-switch, config flag, and source-checkout detection; format_managed_channel_notice() returns channel-specific upgrade hints; _do_update refactored to use the formatter and cache latest version; smoke-check failures standardized with SMOKE_CHECK_FAILED_PREFIX; tests cover precedence, notice formatting, and result invariance.
Silent Auto-Update in Shell Startup
src/pythinker_code/ui/shell/__init__.py
Shell.run() replaces blocking pre-start prompt with non-blocking _schedule_startup_update_task() call. New methods (_silent_auto_update, _run_silent_update_job, _surface_installed_update_notice, _surface_managed_channel_notice, _update_toast, etc.) implement the background update path with conditional UI notices. Background task cleanup catches SystemExit separately to allow graceful continuation after update-installer launch.
Silent Auto-Update Behavior Coverage
tests/ui_and_conv/test_silent_auto_update.py
Comprehensive test module verifying: successful updates emit restart/version toasts; smoke-check failures trigger "verification failed" toast without restart messaging; failed updates remain silent; managed-channel upgrades produce formatted hint toast; throttling/disable logic prevents execution and toasts; startup dispatch routes to _silent_auto_update (enabled), _auto_update (config-disabled), or nothing (env kill-switch); background-task SystemExit does not crash and logs "process exit".
Documentation Update
CHANGELOG.md
Unreleased section notes silent startup auto-updates, opt-out controls (auto_update / PYTHINKER_AUTO_UPDATE), and requirement to restart for updates to take effect.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Pythoughts-labs/pythinker-code#120: Modifies the upgrade execution logic in src/pythinker_code/ui/shell/update.py (_do_update flow), so the main PR's managed-channel refactoring could overlap with Homebrew untrusted-tap trust/retry logic.

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.81% which is insufficient. The required threshold is 70.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format (feat(scope): description), is directly related to the PR's core change (silent startup auto-update), and uses exact terminology matching the changeset.
Description check ✅ Passed Description includes related issue link, detailed explanation of changes, comprehensive testing results, and addresses the template checklist items.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/silent-auto-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread tests/ui_and_conv/test_silent_auto_update.py Fixed
Comment thread tests/ui_and_conv/test_shell_update.py
@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.73684% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pythinker_code/ui/shell/update.py 93.75% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

elkaix added 15 commits June 13, 2026 16:00
The Windows native/pip update path in run_update_job raises SystemExit so
the installer can replace the binary. That exception propagates through the
asyncio task and into _cleanup via t.result(). Since SystemExit is a
BaseException the existing `except Exception` clause did not catch it,
allowing it to escape the done-callback and crash the shell.

Add an `except SystemExit` clause (before `except Exception`) that logs the
event instead of re-raising.

Test uses a _CapturingTask stand-in (monkeypatching asyncio.create_task) to
intercept the registered done-callback and drive it synchronously with a mock
task whose .result() raises SystemExit — necessary because Python 3.14
propagates SystemExit out of asyncio.run() before the callback can be tested
via a live event loop.
Narrow self.soul to PythinkerSoul before reading runtime.config (falling back
to the toast-only path otherwise), cast the resolver test stub to Config, and
apply ruff format across the touched files so make check passes.
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