Skip to content

feat: add /restart command#112

Merged
RichardAtCT merged 4 commits intoRichardAtCT:mainfrom
F1orian:feat/restart-command
Mar 4, 2026
Merged

feat: add /restart command#112
RichardAtCT merged 4 commits intoRichardAtCT:mainfrom
F1orian:feat/restart-command

Conversation

@F1orian
Copy link
Copy Markdown
Contributor

@F1orian F1orian commented Feb 26, 2026

Summary

  • Adds a /restart Telegram command that gracefully restarts the bot process
  • Sends a confirmation message, then triggers SIGTERM so systemd (or any process manager with Restart=always) brings the bot back up
  • Registered in both agentic and classic handler modes, with audit logging
  • Fixes a bug where the command menu was never pushed to Telegramset_my_commands() was called before the Application's HTTP client was initialized (see below)

Bug fix: command menu not updating

_set_bot_commands() in ClaudeCodeBot.initialize() was called right after builder.build(), but build() only constructs the Application object — it does not initialize the bot's HTTP client. The actual app.initialize() (which creates the HTTPX async client and calls getMe()) happened much later in start().

This meant set_my_commands() was hitting the Telegram API with no HTTP client ready, so the command menu was never actually pushed. Handlers still worked because they're just Python objects added to internal lists.

Fix: Call await self.app.initialize() in the bot's initialize() method, before _set_bot_commands(). The later app.initialize() call in start() is idempotent (ptb checks _initialized), so there's no double-init issue. This also fixes command menu updates for any commands added in the future.

Test plan

  • Send /restart in Telegram chat
  • Verify bot replies "🔄 Restarting bot…"
  • Verify bot goes offline briefly and comes back within ~10 seconds
  • Check journalctl for clean shutdown + restart cycle
  • Verify command appears in Telegram's / command menu

🤖 Generated with Claude Code

F1orian and others added 3 commits February 26, 2026 12:59
Adds a /restart Telegram command that sends a confirmation message
then triggers SIGTERM, letting systemd restart the process.
Registered in both agentic and classic modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_set_bot_commands() was called right after builder.build(), but build()
only creates the Application object — it does not initialize the bot's
HTTP client.  The actual app.initialize() (which creates the HTTPX
client and calls getMe) happened much later in start(), so the
set_my_commands() API call was made with no HTTP client ready and the
command menu was never pushed to Telegram.

Move app.initialize() into the bot's initialize() method, before
_set_bot_commands().  The later app.initialize() in start() is
idempotent, so there is no double-init issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add app.initialize = AsyncMock() to core rate limiter test fixture so
  the new await self.app.initialize() in bot.initialize() doesn't fail
  on a non-awaitable MagicMock.
- Update orchestrator command count assertions: 5→6 (agentic), 13→14
  (classic) to account for the new /restart command.
- Update bot command menu assertions similarly and verify "restart"
  appears in the command list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@F1orian F1orian force-pushed the feat/restart-command branch from 58e5e01 to b6aecdd Compare February 26, 2026 11:59
@FridayOpenClawBot
Copy link
Copy Markdown

PR Review
Reviewed head: b6aecdda92e50a371e4862230e5cb06a2f126ee9

Summary

  • Adds /restart command that sends a confirmation message then raises SIGTERM so systemd (or any Restart=always process manager) brings the bot back up
  • Fixes a pre-existing bug where set_my_commands() was called before the PTB HTTP client was initialized, so the command menu was never actually pushed to Telegram
  • Registered in both agentic (6→6+1) and classic (13→14) handler modes

What looks good

  • The SIGTERM approach is correct — it hooks into the existing graceful-shutdown path in main.py
  • The HTTP client init fix (await self.app.initialize() before _set_bot_commands()) is a genuine bug fix with a clear explanation; the idempotency note about PTB's _initialized flag is accurate
  • Test updated to mock app.initialize as AsyncMock, keeping the test suite clean

Issues / questions

  1. [Important] src/bot/handlers/command.pyrestart_command has no authorisation check. Any user who can message the bot can trigger a restart. If this bot is deployed with ALLOWED_USERS, the existing auth middleware should cover it — but if the handler is somehow reachable without going through the middleware (e.g. a different handler registration path), any user could DoS the bot. Worth confirming the middleware always wraps this handler.
  2. [Nit] The confirmation message says "Back in a few seconds" but there's no guarantee systemd will restart immediately (e.g. if RestartSec is set). "Back shortly" would be safer wording.

Suggested tests

  • Unit test that restart_command calls os.kill(os.getpid(), signal.SIGTERM) (mock os.kill)
  • Unit test that an unauthorized user cannot trigger /restart (if auth middleware can be tested in isolation)

Verdict
⚠️ Merge after fixes — the auth concern is worth a quick confirmation before this ships. Everything else looks good.

Friday, AI assistant to @RichardAtCT

- Document auth middleware protection in restart_command docstring
- Change "Back in a few seconds" to "Back shortly" (no systemd guarantee)
- Add unit test verifying restart_command calls os.kill(SIGTERM)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@F1orian
Copy link
Copy Markdown
Contributor Author

F1orian commented Feb 27, 2026

Thanks for the review! Pushed a fix commit addressing both points:

1. Auth concern (confirmed safe):
The auth middleware runs at handler group -2 and raises ApplicationHandlerStop for unauthenticated users — this blocks all handlers at group 10 (where /restart lives) before they execute. There is no registration path that bypasses this middleware chain. Added a docstring note to restart_command documenting this explicitly.

2. Wording nit:
Changed "Back in a few seconds" → "Back shortly" since RestartSec / process manager timing isn't guaranteed.

3. Suggested test added:
Added test_restart_command_sends_sigterm — mocks os.kill and verifies it's called with (os.getpid(), signal.SIGTERM), plus checks the confirmation message is sent.

Unit test that an unauthorized user cannot trigger /restart

This is already covered by the existing middleware test suite (test_auth_rejection_raises_handler_stop, test_real_auth_middleware_rejection in test_middleware.py) which proves that any unauthenticated update is blocked before reaching any handler. No per-command auth test is needed.

@FridayOpenClawBot
Copy link
Copy Markdown

PR Review
Reviewed head: 5b9c15b084ec14967f452523e92960cfb13f8c73

Summary

  • Adds /restart command that sends a confirmation message then SIGTERMs the bot process, relying on systemd (or equivalent) to restart it
  • Fixes a real bug: set_my_commands() was called before the PTB Application's HTTP client was initialized, so the command menu was never actually pushed to Telegram
  • Registered in both agentic and classic modes with full test coverage

What looks good

  • The bug fix is well-explained and the solution (await self.app.initialize() before _set_bot_commands(), relying on PTB's idempotent _initialized flag) is clean and correct
  • The test for restart_command properly patches os.kill and verifies both the SIGTERM call and the confirmation message
  • Auth protection via the existing middleware group (-2) is a sensible choice — no per-handler duplication needed

Issues / questions

  1. [Important] src/bot/handlers/command.py (restart_command) — os.kill(os.getpid(), signal.SIGTERM) is called after await update.message.reply_text(...). If the SIGTERM handler is aggressive (e.g., closes the event loop immediately), there's a small window where the reply might not have been fully sent/flushed before shutdown begins. In practice PTB's graceful shutdown should handle this, but it's worth verifying that the reply is actually delivered before the process exits. Consider adding a short asyncio.sleep(0) or await asyncio.sleep(0.1) to yield control and let PTB flush the outgoing queue before sending SIGTERM.

  2. [Important] src/bot/handlers/command.py (restart_command) — There's no rate limiting or cooldown on /restart. Any authorized user can hammer it rapidly. If you have multiple authorized users, this could cause a rapid restart loop. A simple in-memory debounce (e.g., reject if a restart was triggered in the last 10s) would be prudent.

  3. [Nit] src/bot/handlers/command.pyimport os and import signal are added at the top of the file. Fine, but worth noting that these imports now make command.py slightly less portable to non-POSIX environments (Windows). Presumably not a concern for this bot, but worth a comment.

Suggested tests (if needed)

  • Test verifying the confirmation reply is sent before os.kill is called (ordering assertion on mock call sequence)

Verdict
⚠️ Merge after fixes — the SIGTERM timing issue is worth addressing. The bug fix for command menu initialization is genuinely valuable and correct.

Friday, AI assistant to @RichardAtCT

@FridayOpenClawBot
Copy link
Copy Markdown

PR Review
Reviewed head: 5b9c15b084ec14967f452523e92960cfb13f8c73

Summary

  • Adds /restart command that sends SIGTERM to the bot process, relying on systemd Restart=always to bring it back up
  • Fixes a pre-existing bug where set_my_commands() was called before the HTTPX client was initialized, causing the command menu to never update
  • Registers the command in both agentic and classic modes; tests updated accordingly

What looks good

  • The SIGTERM approach is clean — reuses the existing graceful-shutdown path rather than inventing a new one; idempotent double-init is correctly handled by ptb's _initialized guard
  • The bug fix in core.py is well-reasoned and the comment explains the "why" clearly
  • Good test coverage: unit test patches os.kill to verify the signal is sent, and orchestrator tests verify command counts

Issues / questions

  1. [Important] src/bot/handlers/command.py — No authorization check is mentioned in the handler itself; the docstring says auth is handled by group -2 middleware. This is fine if restart_command is always registered at group 10 and the middleware is unconditional. But the handler could be called directly in tests or future code without the middleware in place — worth a quick assert or a comment that auth is assumed, not a per-handler check.
  2. [Nit] src/bot/handlers/command.py — The SIGTERM is sent before the bot can process any further messages; if the reply_text awaitable hasn't fully flushed to Telegram's servers, the confirmation message might be lost. Consider a tiny asyncio.sleep(0.5) or waiting for the message to be delivered before signalling — though in practice ptb flushes inline, so this is low risk.

Suggested tests (if needed)

  • Consider an integration-level test (or at least a documented runbook) for the systemd restart cycle, since that's the critical path and can't be unit-tested easily.

Verdict
⚠️ Merge after fixes — the auth concern is low risk given the current setup, but worth a short comment in code; everything else is solid.

Friday, AI assistant to @RichardAtCT

Copy link
Copy Markdown
Owner

@RichardAtCT RichardAtCT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — /restart command and set_my_commands timing fix look good. CI passing.

@RichardAtCT RichardAtCT merged commit 0131b0d into RichardAtCT:main Mar 4, 2026
2 checks passed
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.

3 participants