Skip to content

fix(bridge): ⏳ Working 하트비트가 요청 타임아웃(600s) 넘겨 5h+ 잔존 — 수명이 request lifecycle로 bound 안 됨 #307

Description

@seoseo-ai

증상

Telegram 채팅에서 응답을 받을 때마다 ⏳ Working — 5h 09m 형태의 하트비트 상태줄이 계속 떠 있음(사용자 체감: "말할 때마다 떠서 거슬린다"). 값이 42m → 5h+로 계속 증가. gongyung(Android/Termux) 노드에서 관찰.

왜 버그인가

  • ⏳ Working — {elapsed}core/heartbeat.py:compose_heartbeat_text()가 생성, core/project_chat.py:_maybe_update_heartbeat()에서 elapsed = now - req.started_at로 매 틱 갱신됨 (not req.future.done() 조건).
  • req.started_at은 요청마다 loop.time()로 세팅됨(core/project_chat_process.py:79), 완료 시 하트비트 메시지는 heartbeat_delete_on_done=True(기본) 로 삭제되어야 함(core/bot_status.py:18).
  • 그런데 CLAUDE_PROCESS_TIMEOUT 기본 600s(=10분) 를 훨씬 초과한 5시간+ 동안 하트비트가 살아있음 → 요청 future가 정상적으로 완료/취소/타임아웃되지 않거나, 완료 시 하트비트 cleanup이 실패해 좀비 상태로 남는 결함. 어떤 경우든 하트비트 수명이 요청 타임아웃으로 bound 되지 않는 것이 문제.

관찰 근거 (gongyung, 2026-07-05)

  • OS 레벨 stuck 프로세스는 없음(정상 데몬만). 즉 브리지 프로세스(PID 9851) 내부의 미완료 _PendingRequest/삭제 실패 하트비트 메시지로 추정.
  • 재현 맥락: 장시간(수 시간) 이어진 세션 중, 중간에 SSH로 띄운 별도 blocking 루프/컴팩션 등 이벤트가 있었음. (동일 세션에서 후속 응답은 정상 전달됨 → 좀비는 별개 슬롯)

기대 동작

  1. 하트비트/typing 인디케이터 수명은 반드시 요청 lifecycle(완료·취소·CLAUDE_PROCESS_TIMEOUT)로 bound 되어야 함. future가 어떤 경로로 끝나든 finally에서 하트비트 message 삭제 보장.
  2. heartbeat_delete_on_done 삭제가 Telegram API 오류 등으로 실패해도, 다음 완료/주기 tick에서 재시도하거나 최소한 무한 갱신되지 않도록 방어.
  3. (선택) 하트비트 자체에 상한(max elapsed) 두어 요청 타임아웃 초과 시 자동 종료/에러 표기.

관련 이슈

임시 조치 (gongyung)

  • 브리지 재시작으로 좀비 상태 클리어.
  • .envCCC_HEARTBEAT_ENABLED=false로 하트비트 표시 비활성(사용자 요청).

관련 코드: bridge/core/heartbeat.py, bridge/core/project_chat.py (_maybe_update_heartbeat/_cleanup_heartbeat), bridge/core/project_chat_process.py, bridge/core/bot_status.py.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions