Skip to content

fix(telegram): crash-safe poll loop, prompt stop(), visible outages#37

Merged
lesquel merged 1 commit into
mainfrom
fix/telegram-resilience
May 17, 2026
Merged

fix(telegram): crash-safe poll loop, prompt stop(), visible outages#37
lesquel merged 1 commit into
mainfrom
fix/telegram-resilience

Conversation

@lesquel

@lesquel lesquel commented May 17, 2026

Copy link
Copy Markdown
Owner

Summary

Resolves the Telegram resilience item of #33 (Tier-2; convergent across concurrency + silent-failures passes).

# Defect Fix
D1 pollLoop() fire-and-forget, only an inner per-iteration catch → an escaped throw killed the loop silently; Telegram permission buttons go permanently dead Outer try/catch (logs error, backs off, continues while polling) + observable .catch at the call site
D2 sleep() non-abortable → stop() lagged up to ~60s in max backoff interruptibleSleep + AbortController that stop() triggers (fresh controller reinstalled; timer cleared on abort, no leak); both catches guard the sleep with if (polling)
D3 api() swallowed circuit-open/fetch errors → silent null, every send dropped, permission requests hung to timeout with no log Log once on down-transition, once on recovery (closure flag, no per-call spam); still returns null (caller contract unchanged)

Notes

  • Happy path (message send, startup self-check, backoff sequence) is byte-identical — only the error/loop/stop paths changed.
  • Test seam: optional @internal _testOverrides 5th param on createTelegramChannel (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.
  • The fresh review's actionable nit (inner-catch missing the if (polling) guard the outer catch has — a residual ~60s race) is fixed in this PR for full symmetry.

Testing

  • +5 tests: loop logs+continues after an injected crash; stop() settles within an abort window (not ~60s); down/recovered logged once each; no per-call spam.
  • bun test 629/0 · bunx tsc --noEmit clean
  • Independent fresh-context adversarial review: APPROVE, no blocking issues — abort-race proven safe (single-threaded, sync listener registration, fresh controller), tight-loop gated by monotonic backoff, state machine correct, happy path diffed identical.

Independent off main. No file overlap with #34/#35/#36.

Refs #33 (Tier-2, partial — does not close).

…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
@lesquel lesquel merged commit c37c881 into main May 17, 2026
2 checks passed
@lesquel lesquel deleted the fix/telegram-resilience branch May 17, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant