Repository navigation
Conversation
_render_expandable_quote() built raw MarkdownV2 blockquotes as ">line1\n>line2||" — missing the "**" that Telegram's grammar requires to open an *expandable* quote. Without it, the trailing "||" is an unmatched spoiler entity: Telegram rejects the whole sendMessage call with "Can't parse entities", and the caller's fallback resends the message as unformatted plain text with no collapsing at all. This path is hit by most non-trivial notifications: thinking blocks, Bash/Grep/Glob/Task/WebFetch/WebSearch tool_result output, multi-line tool errors, and Edit diffs all go through _format_expandable_quote(). So instead of a collapsed "N lines, tap to expand" block, the full raw content (including repeated tracebacks in log-heavy output) was landing directly in the chat. Fix: promote the first ">" of the rendered block to "**>", matching telegramify_markdown's own render_quote() implementation. Existing tests asserted the old (broken) shape; updated them and added coverage for multi-line and truncated quotes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live notifications for a topic used to accumulate indefinitely: every thinking block, tool_use/tool_result, local-command echo, and even the bot's own echo of the user's outgoing message stayed in the chat forever, alongside the actual assistant answer. For tool-call-heavy sessions (e.g. a monitoring loop polling journalctl) this buried the real answer under a growing wall of technical messages. Add per-topic tracking of the last "secondary" message (_secondary_msg_info, keyed like the existing _status_msg_info / _tool_msg_ids). Secondary means everything except the final assistant text answer: thinking, tool_use, tool_result, local_command, and the role=user echo of a forwarded message. Whenever a new message arrives for a topic, the previously tracked secondary message is deleted; the new message is tracked in its place if it's itself secondary, or left untouched if it's the final answer. Whatever happens to be last simply never gets deleted, so a completed turn naturally leaves only the answer behind — no separate "is this the final message" detection needed. Final text answers are never deleted or tracked, per prior discussion (multiple text blocks in one turn should all persist). tool_use -> tool_result keeps its existing edit-in-place behavior (same message_id, so the secondary tracking stays valid across the edit) rather than being folded into delete+resend. _can_merge_tasks() now also refuses to merge a secondary task with a non-secondary one, since the merged task can only carry one is_secondary value. clear_topic_state() clears the new tracking on topic close. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_process_content_task's rolling delete-on-next logic popped the tracked secondary message out of _secondary_msg_info before attempting bot.delete_message, then swallowed all errors with a bare `except Exception: pass` — which also caught RetryAfter. Under a burst of several messages to the same topic within a couple seconds (thinking + tool_use/tool_result + task-notification text + interactive-UI polling, all hitting the same chat), Telegram's flood control could reject the delete; the exception was silently dropped and the message was already forgotten, so nothing ever cleaned it up. Keep the entry in _secondary_msg_info until the delete actually succeeds (or is given up on for a non-retryable reason), and re-raise RetryAfter so the existing _process_content_with_retry wrapper retries the whole task — a retry now finds the same tracked message and can still delete it, instead of leaking it forever. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_merge_content_tasks folds several secondary entries that arrive close together (task-notification bursts observed at 3-4 messages within tens of milliseconds) into one MessageTask with multiple `parts`. Each part is sent as its own Telegram message, but _secondary_msg_info only recorded the id of the last one — the earlier N-1 messages were sent, immediately forgotten, and never deleted by the rolling cleanup, no matter how many further messages arrived afterward. Same issue applies to a single (unmerged) secondary task whose content gets paginated into multiple messages by split_message. Change _secondary_msg_info's value from a single message_id to a list of ids, collected from every part actually sent (including one converted from a status message). Deletion now walks the whole list; on RetryAfter mid-list, already-deleted ids are dropped and the remainder is written back before re-raising, so a retry of the task resumes exactly where it left off instead of re-deleting or losing track of entries. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TejNote
added a commit
to TejNote/ccbot
that referenced
this pull request
Sep 16, 2026
upstream 패치 자체는 맞지만 **이 fork 의 다른 부분과 겹쳐서 깨지는** 것들이다. upstream 테스트로는 안 잡힌다. 🚨 죽은 토픽 정리 후 타이핑 keepalive 가 살아남아 무한 API 호출 six-ddc#81 의 cleanup_dead_topic 은 clear_topic_state 를 부르지 않는 유일한 해제 경로였다 (다른 셋은 전부 부른다). six-ddc#98 전에는 단순 누수였지만 붙고 나서는 _typing_keepalive 가 while True 라 **텔레그램이 "그 토픽 없다" 고 답한 thread 로 4초마다 send_chat_action 을 영원히** 던지게 됐다. 정리한 (user_id, thread_id) 목록을 반환하도록 바꿔 연결한다. six-ddc#97 의 8줄 스캔이 `·` 불릿을 상태줄로 읽는 구멍을 비인접 줄에 열었다 upstream 조건이 `"…" in line` 하나뿐인데 `·` 는 스피너이면서 평범한 불릿이다. Claude 답변의 `· 첫째 항목은…` 이 상태줄이 되고, six-ddc#98 이 그 값으로 타이핑을 켜므로 유휴 세션에 유령 상태 메시지 + 영구 타이핑이 남는다. 비인접은 `…` **그리고** `·` 아님.⚠️ 구분선 바로 위 불릿은 main 에도 있던 기존 오탐이라 손대지 않았다(실측 대조). 퍼미션 프롬프트 동안 "작업 중" 으로 보이던 것 six-ddc#98 의 set_typing 이 함수 맨 끝 한 번인데 그 위에 조기 return 이 셋이다. 프롬프트는 몇 시간도 떠 있고 실제로는 봇이 사용자를 기다리는 중이다. skip_status 는 제외했다 — 큐가 찬 일시적 상태이고 에이전트는 실제로 작업 중이다. secondary 태스크의 이미지가 고아로 남던 것 six-ddc#94 가 모으는 id 에 _send_task_images 의 사진이 빠졌다. tool_result 는 항상 secondary 라 다음 메시지가 오면 설명 텍스트만 지워지고 스크린샷이 남는다. send_photo 가 id 를 반환한다. 콜백 응답이 최대 45초 지연되던 것 query.answer() 앞에 wait_for_claude_ready(30초) + wait_for_session_map_entry(5~15초)가 있었다. 텔레그램은 약 15초를 넘기면 query is too old 를 내고 try/except 도 없다. 응답을 맨 앞으로 옮겼다 — 결과는 safe_edit 가 본문으로 보여준다. TOPIC_CHECK_INTERVAL docstring 60초 → 5초 정정⚠️ 바인딩당 API 호출 12배로 six-ddc#52 스로틀과 방향이 반대다. 레이트 리밋을 실제로 맞은 관측이 없어 upstream 값은 유지하고, 근거와 되돌릴 값(15~20초)을 주석에 남겼다. Tested: pytest 440항 (신규 8) · pyright 0 errors · ruff 103 → 102, 파일·규칙 단위로 main 에 없던 조합 0건. 신규 테스트는 **뮤테이션 4종**으로 유효성을 확인했다 — 각 수정을 되돌리면 대응 테스트 하나씩만 실패한다. Not-tested: 실제 텔레그램 대상 수동 검증(봇 재시작 필요 — 머지 후 별도) Co-Authored-By: Claude Opus 5 (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
>line||instead of**>line||, so Telegram rejected the message ("Can't parse entities") and every thinking/tool_result/diff notification silently fell back to unformatted, uncollapsed plain text. Fixed by promoting the first>to**>, matchingtelegramify_markdown's ownrender_quote().tool_use → tool_resultkeeps its existing edit-in-place behavior.Test plan
uv run pytest tests/— 267 passeduv run ruff check src/ tests/— clean on changed filesuv run pyright src/ccbot/— 0 errors on changed filesccbotinstance and verified in a real Telegram topicCo-Authored-By: Claude Sonnet 5 noreply@anthropic.com