Fix CLI sync, Telegram error handling, and memU max_tokens - #3
Merged
pufit merged 26 commits intoMar 22, 2026
Conversation
- Add global error handler to Telegram bot polling pipeline - Log incoming messages with truncated preview - Wrap error reply in try/except to prevent secondary failures - Default max_tokens to 4096 in memU bridge when omitted, preventing Anthropic API 400 errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
Author
|
у меня телеграм бот помер. теперь воскрес. надеюсь до утра доживёт |
The polling watchdog previously gave up after 10 consecutive restart failures, leaving the bot permanently dead until a server restart. Now it enters a 5-minute cooldown and retries indefinitely. Also protects the watchdog itself from crashing on unexpected health-check exceptions, and fixes the server lifecycle to properly await channel start/stop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous watchdog only checked if the polling asyncio task was alive, but the task can be alive with a hung connection — silently stuck, not actually receiving updates. Now the watchdog: 1. Tracks last update time across all handlers 2. After 10min with no updates, probes Telegram API (bot.get_me) 3. If the probe times out or fails, force-restarts polling 4. Logs a heartbeat every ~5min so we can verify it's running Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The active probe (bot.get_me) uses a new HTTP connection, so it can succeed even when the polling long-poll connection is silently hung. Now the watchdog force-restarts polling after 15min of staleness regardless of probe results. Also: if updater.stop() hangs during restart (stuck TCP connection), we time out after 15s and rebuild the entire PTB Application from scratch instead of waiting forever. Timeline: 5min stale → probe API → 15min stale → force restart → if stop() hangs → rebuild Application. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two actual bugs fixed, replacing the previous band-aid watchdog: 1. Shutdown order: telegram was stopped AFTER background task cancellation. Starlette runs the lifespan in an anyio context, so cancelling background tasks propagated through cancel scopes and killed the polling task before clean shutdown. Now telegram stops first. 2. Watchdog checked wrong thing: only monitored the Updater (fetches from Telegram), not the Application (processes updates → handlers). If the Application's internal update-fetcher task crashed, updates piled up in the queue but no handlers fired — bot appeared dead while Updater looked healthy. Now checks both, plus queue depth. Also: - TCP keepalive on polling connection (SO_KEEPALIVE + TCP_KEEPIDLE=60s) prevents NAT/firewall from silently dropping the long-poll connection - bootstrap_retries=-1 for infinite retry on transient startup errors - Explicit read/connect timeouts on the polling HTTP client - Simplified watchdog: detect → rebuild, no complex backoff chains Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Streaming messages were sent without parse_mode, causing markdown (bold, links, etc.) to render as plain text. Added ParseMode.MARKDOWN with a fallback to plain text if parsing fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Telegram's ParseMode.MARKDOWN (v1) doesn't support **bold** — only *bold*. Since LLMs emit standard Markdown, messages silently fell back to plain text. Switch to ParseMode.HTML with a md-to-HTML converter that handles bold, italic, code, code fences, and links. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the final streaming edit has identical content to the last intermediate edit, Telegram returns "message is not modified". The fallback was replacing the already-formatted HTML message with plain text, stripping all formatting. Now we detect this case and return early instead of falling back. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Downloads and base64-encodes images from Telegram, passes them through the channel router to the agent engine as multi-modal content blocks. Handles both single photos and media groups (albums) by buffering album parts before sending as one message. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…densation The Anthropic SDK prepends /v1 internally, so including it in base_url caused /v1/v1/messages 404 errors. Also adds a proxy-compatible condensation path using httpx for OpenAI-compatible endpoints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two tests leaked host state by not mocking ~/.claude/.credentials.json, causing them to find the real OAuth token instead of testing the intended waterfall paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of showing repeated `[tool: X]` lines, group them as `[tool: X] x3` for cleaner streaming output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… running sessions PTB processes updates sequentially by default, which means /stop gets queued behind the active message handler and only runs after it finishes. concurrent_updates(True) dispatches each update in its own asyncio task. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When /stop arrives before _run_inner() finishes creating the SDK client, stop_session() found nothing to interrupt and returned False. Now it sets a deferred stop flag that _run_inner() checks right after client creation, aborting the turn before sending the message to the agent. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
/new now stops the running session before creating a replacement, preventing orphaned sessions. `nerve restart` spawns a detached helper so it works even when invoked from inside the daemon (e.g. via Telegram). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…essions interrupt() only signals the CLI to stop — it doesn't guarantee receive_response() will return. Without also cancelling the task, _run_inner hangs forever waiting for a ResultMessage that never comes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When /stop cancels a turn, ResultMessage (the normal source of sdk_session_id) never arrives. Two changes: 1. Early-capture sdk_session_id from the first message that carries it (e.g. StreamEvent) instead of waiting for ResultMessage. 2. In the CancelledError handler, write the captured sdk_session_id to the DB before marking the session stopped. Without this, new sessions stopped before their first turn completes lose their sdk_session_id and cannot be resumed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After /stop, a deferred-stop flag could linger from the previous turn and immediately kill the next user message. Now engine.run() clears stale flags before marking a new turn as running. Also: wrap engine.run() in router as a Task so /stop can cancel it reliably, and reorder CancelledError cleanup to persist sdk_session_id before non-critical work (message saving, broadcast, memorize). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After interrupt(), wait up to 5s for the turn to complete gracefully via ResultMessage. This keeps the SDK client alive so the next message continues instantly without reconnecting. Only falls back to task cancellation if interrupt doesn't work within the timeout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove redundant "tool:" prefix, collapse consecutive tool labels without extra blank lines, and add a separator when transitioning from tool block back to text. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a user replies to a message or selects a quote, prepend structured context so the model knows what is being referenced. Works for both single messages and album (media group) messages. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add reaction handler that forwards emoji reactions to the agent as text messages, with an LRU cache (200 entries) to include the original message context. Fix allowed_updates to explicitly request all update types — the default set excludes message_reaction and auto-detection only works via Application.run_polling(), not Updater.start_polling(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a `react` tool that sets emoji reactions on the user's last message. Adds REACTIONS capability to the channel model, tracks inbound message context in the router, and implements Telegram's setMessageReaction API. Also resolves premium custom emoji IDs to their base emoji in reaction-update handling. Co-Authored-By: Claude Opus 4.6 <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
nerve synccommand afterIngestResultrefactor — update field names and remove staleengineargument frombuild_source_runnersmax_tokensto 4096 when omitted, preventing Anthropic API 400 errorsTest plan
nerve syncand verify it completes without errorsmax_tokens🤖 Generated with Claude Code