fix(telegram): crash-safe poll loop, prompt stop(), visible outages#37
Merged
Conversation
…visible Second-wave audit (#33 Tier-2), convergent across two review passes. - D1: pollLoop() was fire-and-forget with only a per-iteration inner catch — anything escaping it killed the loop silently and Telegram permission buttons became permanently unresponsive. Added an outer try/catch (logs at error, backs off, continues while polling) plus an observable .catch at the call site so a fatal exit still surfaces. - D2: sleep() was non-abortable, so stop() could lag up to ~60s while the loop sat in max backoff. Replaced with interruptibleSleep backed by an AbortController that stop() triggers (then installs a fresh controller); timer is cleared on abort (no leak). Inner + outer catch both guard the sleep with `if (polling)` for symmetry — closes the narrow race where stop() fires just before the sleep starts. - D3: api() swallowed circuit-open/fetch errors returning null with zero logging — a tripped circuit silently dropped every Telegram send (permission requests hung to timeout). Now logs once on the down transition and once on recovery (closure flag, no per-call spam); still returns null (caller contract unchanged). Test seam: optional @internal `_testOverrides` 5th param on createTelegramChannel (backoff steps / breaker / loop-settled hook) — all production callers unchanged (verified). +5 tests. bun test 629/0, tsc clean. Independent fresh-context review: APPROVE, no blocking issues (abort-race proven safe, tight-loop gated by backoff, happy path byte-identical). The actionable nit (inner-catch guard symmetry) is fixed here. Refs #33
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
Resolves the Telegram resilience item of #33 (Tier-2; convergent across concurrency + silent-failures passes).
pollLoop()fire-and-forget, only an inner per-iteration catch → an escaped throw killed the loop silently; Telegram permission buttons go permanently deaderror, backs off, continues whilepolling) + observable.catchat the call sitesleep()non-abortable →stop()lagged up to ~60s in max backoffinterruptibleSleep+AbortControllerthatstop()triggers (fresh controller reinstalled; timer cleared on abort, no leak); both catches guard the sleep withif (polling)api()swallowed circuit-open/fetch errors → silentnull, every send dropped, permission requests hung to timeout with no lognull(caller contract unchanged)Notes
@internal_testOverrides5th param oncreateTelegramChannel(inject backoff steps / breaker / loop-settled hook). All production callers verified unchanged (server/index.ts+ the 2 integration tests pass ≤4 args). Not in the package public exports.if (polling)guard the outer catch has — a residual ~60s race) is fixed in this PR for full symmetry.Testing
stop()settles within an abort window (not ~60s); down/recovered logged once each; no per-call spam.bun test629/0 ·bunx tsc --noEmitcleanIndependent off
main. No file overlap with #34/#35/#36.Refs #33 (Tier-2, partial — does not close).