Repository navigation
Conversation
parse_status_line checks the first non-blank line above the chrome separator
and returns None if it isn't a spinner. Claude Code now renders a tip block
between the status line and the chrome:
✳ Gitifying… (2m 1s · ↓ 6.5k tokens)
⎿ Tip: Use /btw to ask a quick side question without interrupting
current work
────────────────────────────────────────
❯
so the scan stops on "current work" and reports no status. Whenever a tip is
showing, the topic's status message goes stale and the typing indicator never
lights.
Keep scanning upward instead. The line directly above the separator is still
accepted on the spinner glyph alone, preserving current behaviour; past that,
a line must also carry the ellipsis of a live status. That keeps out both the
prose bullets the original guard was written for and the finished
"✻ Sautéed for 7s" marker.
The indicator fires only when the status text contains "esc to interrupt".
Claude Code moved that string out of the status line into the footer hint
under the input box, so the condition is now never true and the indicator
never appears:
✳ Gitifying… (2m 1s · ↓ 6.5k tokens) <- status line, no "esc"
────────────────────────────────────
❯
────────────────────────────────────
⏸ manual mode on · esc to interrupt <- footer, not parsed
Even before that, a single send_chat_action covered roughly the first five
seconds of a turn; Telegram expires a chat action after that.
Drive the indicator from status polling instead, which already runs once a
second and is the only place that knows whether a session is mid-turn: a
parsed status line means working, no status line means idle. A keepalive task
re-sends the action every 4s and is cancelled on idle, on status clear, on
topic cleanup, and at shutdown. The action carries message_thread_id so it
lands in the right forum topic, and a failed send logs at debug rather than
being swallowed.
Depends on the tip-block parsing fix — without it parse_status_line returns
None while a tip is showing, and the indicator drops out mid-turn.
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.
Problem
The typing indicator fires only when the status text contains
esc to interrupt:Claude Code moved that string out of the status line and into the footer hint below the input box, so the condition is now never true and the indicator never appears:
The status line samples in #52 show the same thing — none of them carry the string.
Even before the UI change, this only half worked: Telegram expires a chat action after ~5s, and a single
send_chat_actionfired per status change. A long turn showed the dots briefly and then went silent.Fix
Drive the indicator from status polling, which already runs once a second and is the only place that knows whether a session is mid-turn — a parsed status line means working, no status line means idle. A keepalive task re-sends the action every 4s and is cancelled on idle, on status clear, on topic cleanup, and at shutdown.
Two smaller things fixed along the way:
message_thread_id, so it lands in the right forum topic rather than the group's General (every other send inmessage_queue.pyalready passes it via_send_kwargs)except Exception: passNote for reviewers
#52 also touches
src/ccbot/handlers/status_polling.py. The overlap is small — this adds three lines next to the existingparse_status_linecall, that one adds throttling around the enqueue — but whichever merges second will want a look.