feat(progress): hot-reload [progress] settings without restart (#269)#447
Merged
Nathan Schram (nathanschram) merged 1 commit intodevfrom Apr 27, 2026
Conversation
Closes #269. The four settings groups in the issue had different states: - [footer]: already loads fresh per-message via _load_footer_settings (no work) - [cost]: already loads fresh per-call inside _check_cost_budget (no work) - [watchdog]: already loads fresh per-run via _load_watchdog_settings at the top of handle_message (no work — verified, applies on next run) - [progress]: was baked in at startup via MarkdownFormatter constructor + ExecBridgeConfig.min_render_interval — this PR closes that gap Changes: - markdown.py: new MarkdownFormatter.refresh_from(progress_settings) updates max_actions + verbosity from a fresh ProgressSettings snapshot. Tolerates missing/invalid attributes (clamps negative max_actions to 0; ignores unknown verbosity values). - telegram/bridge.py: new TelegramPresenter.refresh_progress_settings() delegates to formatter.refresh_from. - runner_bridge.py: new _load_progress_settings() sibling of _load_footer_settings / _load_watchdog_settings; handle_message reads it fresh per-run, calls cfg.presenter.refresh_progress_settings(...) via duck-typed getattr (Presenter is a Protocol, so we don't add to it), and threads progress_cfg.min_render_interval into each ProgressEdits instance instead of the startup snapshot. Per-chat /verbose overrides downstream of _resolve_presenter reconstruct from the refreshed defaults. Out of scope (entry-point limitation): engine + command registration still require pipx upgrade / restart. Documented on the issue. 8 new tests in tests/test_meta_line.py: TestMarkdownFormatterRefresh covers max_actions update, verbosity update, negative clamp, invalid-verbosity rejection, missing-attribute tolerance, presenter delegation. Plus _load_progress_settings defaults / error-fallback. Full suite: 2511 passed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Nathan Schram (nathanschram)
added a commit
that referenced
this pull request
Apr 27, 2026
All 9 v0.35.3 Group 2 issues now landed on dev: - #404 — secret-scanning alert (PR #439) - #297 — /trigger → /listen rename + alias (PR #440) - #294 — master trigger pause/resume toggle (PR #441) - #380 — auto-approve scope review (PR #442) - #438 — claude_stream_idle_timeout_ms + Type-A/B classification (PR #443) - #410 — subscription usage observability + /usage debug (PR #444) - #271 — trigger visibility Tier 2 + Tier 3 (PR #445) - #333 — Claude post-result idle timeout + ✓ turn complete UX hint (PR #446) - #269 — hot-reload [progress] settings (PR #447) Bumps to TestPyPI for staging via @hetz_lba1_bot once integration tests U1-U7 pass against @untether_dev_bot. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #269. Final hot-reload gap closed: editing
[progress].max_actions,[progress].verbosity,[progress].min_render_interval, or[progress].group_chat_rpsinuntether.tomlnow applies on the next run without restarting the bot.The four settings groups the issue called out were in different states:
[footer]_load_footer_settings()[cost]_check_cost_budget()[watchdog]_load_watchdog_settings()at the top ofhandle_message[progress]MarkdownFormatter(max_actions, verbosity)constructor +ExecBridgeConfig.min_render_intervalWhat changed
src/untether/markdown.py— newMarkdownFormatter.refresh_from(progress_settings)updatesmax_actions+verbosityfrom a freshProgressSettingssnapshot. Tolerates missing/invalid attributes (clamps negativemax_actionsto 0; ignores unknown verbosity values).src/untether/telegram/bridge.py— newTelegramPresenter.refresh_progress_settings()delegates toformatter.refresh_from.src/untether/runner_bridge.py— new_load_progress_settings()sibling of_load_footer_settings/_load_watchdog_settings;handle_messagereads it fresh per-run, callscfg.presenter.refresh_progress_settings(...)via duck-typedgetattr(Presenter is a Protocol, so we don't add to it), and threadsprogress_cfg.min_render_intervalinto eachProgressEditsinstance instead of the startup snapshot./verboseoverrides are unaffected:_resolve_presenteralready reconstructs the override formatter from the default presenter's current values per-call, so refreshing the default's formatter feeds through.Out of scope (entry-point limitation, documented on the issue): engine registration and command registration still require
pipx upgrade/ restart.Tests
8 new in
tests/test_meta_line.py:TestMarkdownFormatterRefresh::test_refresh_updates_max_actionsTestMarkdownFormatterRefresh::test_refresh_updates_verbosityTestMarkdownFormatterRefresh::test_refresh_clamps_negative_max_actions_to_zeroTestMarkdownFormatterRefresh::test_refresh_ignores_invalid_verbosityTestMarkdownFormatterRefresh::test_refresh_tolerates_missing_attributesTestMarkdownFormatterRefresh::test_telegram_presenter_refresh_delegates_to_formattertest_load_progress_settings_returns_defaults_when_missingtest_load_progress_settings_returns_defaults_on_errorFull suite: 2511 passed, 2 skipped.
Test plan
uv run pytest tests/test_meta_line.py tests/test_exec_bridge.py tests/test_verbose_progress.py tests/test_verbose_command.py --no-cov(clean)uv run pytest --no-cov(2511 passed)uv run ruff format src/ tests/(clean)uv run ruff check src/ tests/(clean)@untether_dev_bot(after merge to dev):[progress].max_actions = 8in~/.untether-dev/untether.toml, save; next run shows up to 8 actions instead of the previous 5[progress].verbosity = "verbose", save; next run renders with full tool detail without/verbosetoggle[progress].min_render_interval = 1.5, save; next run throttles edits at 1.5s spacing without restart[watchdog].tool_timeout = 1200, save; next run extends tool stall threshold (already worked pre-feat: hot-reload support for triggers, watchdog, and progress settings #269 but worth confirming as part of the four-area sweep)[footer].show_subscription_usage = false, save; next run footer drops the subscription block[cost].max_cost_per_run, save; next run picks up the new threshold🤖 Generated with Claude Code