Skip to content

feat: v0.35.1rc4 — /at, hot-reload bridge, trigger visibility, restart Tier 1#292

Merged
Nathan Schram (nathanschram) merged 6 commits intodevfrom
feature/v0.35.1rc4
Apr 14, 2026
Merged

feat: v0.35.1rc4 — /at, hot-reload bridge, trigger visibility, restart Tier 1#292
Nathan Schram (nathanschram) merged 6 commits intodevfrom
feature/v0.35.1rc4

Conversation

@nathanschram
Copy link
Copy Markdown
Member

Summary

Bundles six items for v0.35.1rc4:

Full details and CHANGELOG entries in CHANGELOG.md under ## v0.35.1.

Test plan

  • 2164 unit tests pass (81.55% coverage)
  • uv run ruff format --check + ruff check clean
  • uv lock --check clean
  • scripts/validate_release.py passes (skips rc per discipline)
  • Integration tests via @untether_dev_bot — tier 7 command smoke, tier 1 all-engines, plus new rc4 scenarios R1-R10 (see docs/reference/integration-testing.md)
  • Staging dogfood on @hetz_lba1_bot for 1+ week before v0.35.1 stable tag

New files

  • src/untether/sdnotify.py — stdlib sd_notify client
  • src/untether/telegram/at_scheduler.py/at module-level scheduler
  • src/untether/telegram/commands/at.py/at backend
  • src/untether/telegram/offset_persistence.py — update_id persistence
  • src/untether/triggers/describe.pydescribe_cron() utility
  • tests/test_at_command.py, test_bridge_config_reload.py, test_describe_cron.py, test_offset_persistence.py, test_sdnotify.py, test_trigger_meta_line.py

Docs

  • CHANGELOG.md v0.35.1 section: 6 new entries
  • CLAUDE.md features + key files + tests
  • README.md/at in commands table, trigger visibility in Scheduled tasks bullet
  • docs/reference/triggers/triggers.md — Hot-reload section, Trigger visibility section, run_once flag
  • docs/reference/commands-and-directives.md/at row
  • docs/reference/dev-instance.mdType=notify + RestartSec=2 sections
  • docs/reference/integration-testing.md — Q14-Q16 + new rc4 scenarios R1-R10
  • docs/how-to/webhooks-and-cron.mdrun_once, /at, discovery sections
  • .claude/rules/telegram-transport.md — hot-reload + sd_notify + /at sections
  • .claude/rules/runner-development.md — RunContext.trigger_source

🤖 Generated with Claude Code

systemd user services inherit OOMScoreAdjust=200 + OOMPolicy=stop
defaults, which made Untether's engine subprocesses preferred
earlyoom/kernel OOM killer targets ahead of CLI claude
(oom_score_adj=0) and orphaned grandchildren actually consuming the
RAM. When lba-1 ran low on RAM, live Telegram chats died with rc=143
(SIGTERM) while the processes actually eating the RAM survived.

Updates contrib/untether.service with:

- OOMScoreAdjust=-100 — documents intent; kernel clamps to the parent
  baseline for unprivileged users (typically 100), but takes effect
  if the parent user@UID.service is ever overridden lower
- OOMPolicy=continue — a single OOM-killed child no longer tears
  down the whole unit cgroup; previously every live chat died at once

Also updates docs/reference/dev-instance.md with a new OOM section
covering the asymmetry, the clamping caveat, and the optional
sudo systemctl edit user@UID.service override for operators who
want Untether's children to live longer than CLI processes.

Existing installs need to copy the unit file and
`systemctl --user daemon-reload`; staging picks up the change on
the next `scripts/staging.sh install` cycle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ibility, restart Tier 1 (#271, #286, #287, #288)

Bundles four rc4 features plus a CHANGELOG entry for #283 (diff_preview gate,
already on dev as 8c04904). Full details in CHANGELOG.md.

#269/#285 hot-reload triggers: merged separately as PR #285 (squash-merged to
dev); this commit extends TriggerManager with rc4 helpers (remove_cron,
crons_for_chat, webhooks_for_chat, cron_ids, webhook_ids) for Features 4b and
5 below.

#288 — /at command and run_once cron flag:
- new telegram/at_scheduler.py — module-level task-group + run_job holder;
  schedule_delayed_run(), cancel_pending_for_chat(), active_count();
  per-chat cap of 20 pending delays
- new telegram/commands/at.py — AtCommand backend, /at <duration> <prompt>
  with Ns/Nm/Nh suffixes, 60s-24h range
- /cancel integration via cancel_pending_for_chat()
- drain integration via active_count() in _drain_and_exit
- entry-point at = untether.telegram.commands.at:BACKEND
- CronConfig.run_once: bool = False; scheduler removes cron after fire
  if run_once=True; re-enters on reload/restart

#286 — unfreeze TelegramBridgeConfig:
- drop frozen=True (slots preserved); add update_from(settings) method
- route_update() reads cfg.allowed_user_ids live; handle_reload() calls
  update_from() and refreshes state.forward_coalesce_s / media_group_debounce_s
- restart-only keys still warn (bot_token, chat_id, session_mode, topics,
  message_overflow); others hot-reload

#271 — trigger visibility Tier 1:
- new triggers/describe.py — describe_cron(schedule, timezone) utility
- /ping shows per-chat trigger indicator when triggers target the chat
- RunContext.trigger_source field; dispatcher sets it to cron:<id>/webhook:<id>;
  runner_bridge seeds progress_tracker.meta['trigger'] with icon + source;
  ProgressTracker.note_event merges engine meta over dispatcher meta
- format_meta_line() appends 'trigger' to footer parts
- CommandContext gains trigger_manager, default_chat_id fields (default None);
  populated by telegram/commands/dispatch.py from cfg

#287 — graceful restart Tier 1:
- new sdnotify.py — stdlib sd_notify client (READY=1 / STOPPING=1);
  poll_updates sends READY=1 after _send_startup succeeds;
  _drain_and_exit sends STOPPING=1 at drain start
- new telegram/offset_persistence.py — DebouncedOffsetWriter; loads saved
  update_id on startup, persists via on_offset_advanced callback in
  poll_incoming; flushes in poll_updates finally block
- contrib/untether.service: Type=notify, NotifyAccess=main, RestartSec=2

Tests: +224 tests added across 6 new test files and 6 extended files;
2164 total tests pass with 81.55% coverage.

Context files (CLAUDE.md, .claude/rules/*) and human docs (README, triggers
reference, dev-instance, integration-testing, webhooks-and-cron how-to,
commands-and-directives) updated. rc4 integration test scenarios R1-R10
added to integration-testing.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 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: d1617c12-ae18-43c4-925c-97f146f36a82

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 feature/v0.35.1rc4

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.

anyio.CancelScope.__exit__ swallows the Cancelled exception when the
scope itself caused the cancellation. The fire/dispatch code outside
the scope continued regardless. Added cancelled_caught check after
the scope exits to prevent stale timers from dispatching.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52-test plan covering all rc4 features: /at command, run_once,
hot-reload (triggers + bridge config), trigger visibility,
graceful restart Tier 1, plus standard Tier 1/6/7 regression.

Includes correct dev bot chat IDs (Bot API + Telethon MCP mapping),
pre-test trigger config, results template, and known caveats.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nathanschram
Copy link
Copy Markdown
Member Author

Integration Test Report — v0.35.1rc4

Date: 2026-04-14
Bot: @untether_dev_bot (local editable source)
Branch: feature/v0.35.1rc4

Results: 46/52 pass, 0 fail, 6 skip

Phase Tests Pass Fail Skip Notes
Tier 7 (commands) 12 12 0 0 Q1-Q12: /ping, /config, /cancel, /verbose, /stats, /ctx, /agent, /trigger, /file, /at edge cases
/at command (#288) 4 3 0 1 AT1-AT3 pass; AT4 (cap=20) skip — unit tests cover
run_once cron (#288) 2 2 0 0 RO1-RO2: fire once + reload re-enables
Hot-reload triggers (#269) 5 5 0 0 HR1-HR5: add/remove/secret/health all work
Hot-reload bridge (#286) 3 3 0 0 BR1-BR3: voice toggle + restart-only warning
Trigger visibility (#271) 4 4 0 0 TV1-TV4: /ping trigger line + cron/webhook footers
Graceful restart (#287) 4 2 0 2 GR1+GR3+GR4 pass; GR2 skip (dev unit is Type=simple)
OOM + diff_preview (#275, #283) 2 1 0 1 OOM1 pass; DP1 skip (complex interactive)
Tier 1 engines (U1+U6) 12 10 0 2 5/6 engines pass; AMP 402 (upstream billing); U6 cancel verified
Tier 6 stress 3 2 0 1 S2+S7 pass; S3 skip (covered by GR tests)
Log inspection 1 1 0 0 No zombies, 58 FDs, clean logs

Detailed results posted to individual issues

Non-Untether findings

  1. AMP 402: amp -x requires paid credits — upstream billing issue, not Untether
  2. Gemini NUL byte: One jsonl.msgspec.invalid warning per run (byte 0x00 in stream) — already handled gracefully by GeminiRunner.decode_error_events() which drops the malformed line and continues; no user impact
  3. Telegram MCP entity cache: Some group chats require negative Bot API IDs (-5156256333) instead of positive Telethon IDs (5156256333) — known StringSession cold cache issue in the MCP, not Untether

Verdict

rc4 is ready for staging. All new features work correctly in live Telegram. No Untether bugs found during testing.

The old ut-dev-hf: chat IDs (5171122044 etc.) belong to a different
bot (ID 8485467124). Updated both docs to the current @untether_dev_bot
chats with both Telethon and Bot API ID forms. Added note about
Telegram MCP PeerUser fallback for channel/supergroup IDs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HIGH priority:
- config.md: add run_once to cron table, fix watch_config description
  to list hot-reloadable vs restart-only settings
- operations.md: fix hot-reload section (transport settings ARE now
  partially hot-reloadable), add /ping trigger format, update_id
  persistence, systemd section with Type=notify/OOM notes
- schedule-tasks.md: add /at command section with examples, run_once
  mention

MEDIUM priority:
- triggers.md: remove duplicate hot-reload section, keep authoritative
  version with watch_config requirement and last_fired note
- CLAUDE.md: add diff_preview plan bypass (#283) to features list
- troubleshooting.md: add entries for config hot-reload issues and
  /at delay not firing

LOW priority:
- security.md: document untrusted-payload prefix for webhooks/cron
- voice-notes.md: note that voice settings hot-reload
- specification.md: bump version to v0.35.1
- tutorials: update version numbers from 0.35.0 to 0.35.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nathanschram Nathan Schram (nathanschram) merged commit 5951851 into dev Apr 14, 2026
20 of 21 checks passed
@nathanschram Nathan Schram (nathanschram) deleted the feature/v0.35.1rc4 branch April 14, 2026 07:51
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