Skip to content

feat(progress): hot-reload [progress] settings without restart (#269)#447

Merged
Nathan Schram (nathanschram) merged 1 commit intodevfrom
feat/269-hot-reload-watchdog-progress-footer-cost
Apr 27, 2026
Merged

feat(progress): hot-reload [progress] settings without restart (#269)#447
Nathan Schram (nathanschram) merged 1 commit intodevfrom
feat/269-hot-reload-watchdog-progress-footer-cost

Conversation

@nathanschram
Copy link
Copy Markdown
Member

Summary

Closes #269. Final hot-reload gap closed: editing [progress].max_actions, [progress].verbosity, [progress].min_render_interval, or [progress].group_chat_rps in untether.toml now applies on the next run without restarting the bot.

The four settings groups the issue called out were in different states:

Section Starting state Action
[footer] Already loaded fresh per-message via _load_footer_settings() None — verified
[cost] Already loaded fresh per-call inside _check_cost_budget() None — verified
[watchdog] Already loaded fresh per-run via _load_watchdog_settings() at the top of handle_message None — verified, applies on next run
[progress] Baked in at startup via MarkdownFormatter(max_actions, verbosity) constructor + ExecBridgeConfig.min_render_interval Closed by this PR

What changed

  • src/untether/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).
  • src/untether/telegram/bridge.py — new TelegramPresenter.refresh_progress_settings() delegates to formatter.refresh_from.
  • src/untether/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 are unaffected: _resolve_presenter already 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_actions
  • TestMarkdownFormatterRefresh::test_refresh_updates_verbosity
  • TestMarkdownFormatterRefresh::test_refresh_clamps_negative_max_actions_to_zero
  • TestMarkdownFormatterRefresh::test_refresh_ignores_invalid_verbosity
  • TestMarkdownFormatterRefresh::test_refresh_tolerates_missing_attributes
  • TestMarkdownFormatterRefresh::test_telegram_presenter_refresh_delegates_to_formatter
  • test_load_progress_settings_returns_defaults_when_missing
  • test_load_progress_settings_returns_defaults_on_error

Full 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)
  • Integration on @untether_dev_bot (after merge to dev):
    • Edit [progress].max_actions = 8 in ~/.untether-dev/untether.toml, save; next run shows up to 8 actions instead of the previous 5
    • Edit [progress].verbosity = "verbose", save; next run renders with full tool detail without /verbose toggle
    • Edit [progress].min_render_interval = 1.5, save; next run throttles edits at 1.5s spacing without restart
    • Edit [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)
    • Edit [footer].show_subscription_usage = false, save; next run footer drops the subscription block
    • Edit [cost].max_cost_per_run, save; next run picks up the new threshold

🤖 Generated with Claude Code

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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 094e9a08-a316-486c-a97a-ccf03329da9e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/269-hot-reload-watchdog-progress-footer-cost

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.

@nathanschram Nathan Schram (nathanschram) merged commit d34bdf6 into dev Apr 27, 2026
21 checks passed
@nathanschram Nathan Schram (nathanschram) deleted the feat/269-hot-reload-watchdog-progress-footer-cost branch April 27, 2026 08:18
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>
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