Commite0a04619Recorded29 Jun 2026Repositorycourier
Bound Telegram poll + send reads with request-timeout
Message
Pass a 25s request-timeout (below the 30s poller stale threshold) into the poller's make-tg-bot and into the leader-side send-message tg-client, so a half-open/blackholed TLS read raises instead of blocking. The poller's poll-loop guard then logs, backs off, and reconnects; the leader-side send fails cleanly instead of freezing the whole leader (the likeliest cause of needing a manual /mcp re-init).
Consumes the new sigil-telegram request-timeout (which threads through to sigil-http 0.16.3's opt-in timeout: keyword). The media-upload client also sets it for consistency, but uploads read over raw TLS in sigil-telegram's upload path, so that bound is not yet effective there (noted inline as a follow-up).
Changed
src/courier/poller.sgl | 10 +++++++++-
src/courier/telegram.sgl | 22 ++++++++++++++++++++--
2 files changed, 29 insertions(+), 3 deletions(-)Diff
src/courier/poller.sglmodified
@@ -66,6 +66,13 @@
66
(define *error-backoff-max* 60) 67
(define *heartbeat-slice* 5) ;; max seconds between heartbeats 68
+69
;; HTTP read timeout for each poll's getUpdates request. Kept BELOW+70
;; *poller-stale-threshold* (30s) so a stalled/half-open read raises+71
;; (caught by the poll-loop guard -> log + backoff + reconnect)+72
;; before the supervisor watchdog would SIGKILL the child. Without+73
;; it a blackholed TLS read blocks past the threshold every time.+74
(define *poll-request-timeout* 25) ;; seconds; must be < stale threshold+75
76
;; Supervisor: wedge detection and restart backoff 77
(define *poller-stale-threshold* 30) ;; seconds of child silence 78
(define *watchdog-interval* 10) ;; seconds between checks@@ -161,7 +168,8 @@
168
(: courier-config? -> void?) 169
(let* ((token (courier-config-telegram-token config)) 170
(allowed (courier-config-allowed-senders config))−164
(bot (make-tg-bot token: token)))+171
(bot (make-tg-bot token: token+172
request-timeout: *poll-request-timeout*))) 173
174
(poller-emit-line! 175
(json-encode `((type . "hello") (pid . ,(process-id)))))src/courier/telegram.sglmodified
@@ -24,6 +24,14 @@
24
format-size) 25
(begin 26
+27
;; HTTP read timeout (seconds) for leader-side Telegram sends. A+28
;; blackholed send-response read on the leader's blocking TLS read+29
;; would otherwise freeze the whole leader (MCP servicing, watchdog,+30
;; poller supervision) until the OS TCP timeout -- the likeliest+31
;; cause of needing a manual /mcp re-init. Bounding the read makes a+32
;; stalled send raise promptly so the tool call fails cleanly.+33
(define *send-request-timeout* 25)+34
35
;; ============================================================ 36
;; Send Message Tool 37
;; ============================================================@@ -63,7 +71,10 @@
71
default-chat-id))) 72
(if (and token chat-id) 73
(begin−66
(tg-send-message (tg-client token: token) chat-id text)+74
(tg-send-message+75
(tg-client token: token+76
request-timeout: *send-request-timeout*)+77
chat-id text) 78
(log-info "Telegram message sent" chat-id: chat-id) 79
"Message sent.") 80
"Error: Telegram not configured (missing token or chat ID)"))))))))))@@ -140,7 +151,14 @@
151
"Error: no chat ID (set COURIER_TELEGRAM_CHAT_ID or pass 'to')" 152
(let* ((kind (resolve-media-type type-arg path)) 153
(size (file-size path))−143
(client (tg-client token: token)))+154
;; NOTE: media uploads go through tg-api-call/upload,+155
;; which reads over a raw TLS connection (not+156
;; sigil-http's http-post/json), so this timeout is+157
;; NOT yet enforced on the upload read -- set for+158
;; consistency/future-proofing. Bounding uploads needs+159
;; a timeout on sigil-telegram's upload path (follow-up).+160
(client (tg-client token: token+161
request-timeout: *send-request-timeout*))) 162
(upload-by-kind client chat-id kind path caption-arg) 163
(log-info "Telegram media sent" 164
chat-id: chat-id kind: kind