Skip to content

fix: replace Telegraf with grammY to fix Bun TypeError crash#1066

Merged
coleam00 merged 2 commits intodevfrom
archon/task-fix-issue-1042
Apr 16, 2026
Merged

fix: replace Telegraf with grammY to fix Bun TypeError crash#1066
coleam00 merged 2 commits intodevfrom
archon/task-fix-issue-1042

Conversation

@coleam00
Copy link
Copy Markdown
Owner

@coleam00 coleam00 commented Apr 10, 2026

Summary

  • Replaces telegraf with grammy in packages/adapters to fix a crash on Bun where Telegraf's internal redactToken() function assigns to a non-writable error.message property, throwing TypeError: Attempted to assign to readonly property on every bot.launch() call
  • Telegraf v4 is end-of-life (final release v4.16.3, Feb 2024) and fundamentally incompatible with Bun's strict ESM property handling; grammY uses native fetch (no node-fetch) and has no error-mutation patterns
  • Preserves all existing behavior: 409 retry logic, dropPendingUpdates, auth checks, message splitting, and the adapter's public interface

Changes

File Action
packages/adapters/package.json Replace telegraf ^4.16.0 with grammy ^1.36.0
packages/adapters/src/chat/telegram/adapter.ts Migrate from Telegraf API to grammY API (Bot class, bot.api.* calls, message:text filter, bot.start() with onStart callback)
packages/adapters/src/chat/telegram/adapter.test.ts Update mock targets from telegraf to grammy types; update launchstart mock with onStart callback pattern
bun.lock Auto-updated by bun install

Key API differences handled:

  • bot.telegram.sendMessagebot.api.sendMessage
  • bot.launch({ dropPendingUpdates })bot.start({ drop_pending_updates, onStart }) wrapped in a Promise that resolves on onStart (grammY's start() only resolves when the bot stops)
  • bot.on('message', ...) with manual 'text' in ctx.message check → bot.on('message:text', ...) filter
  • handlerTimeout: Infinity removed — grammY has no handler timeout by default

Validation

All checks pass on the migrated code:

Check Result
bun run type-check ✅ 0 errors (9 packages)
bun run lint ✅ 0 errors, 0 warnings
bun run format:check ✅ Pass
bun run test ✅ All passed, 0 failed

Fixes #1042

Summary by CodeRabbit

  • Refactor

    • Updated Telegram adapter with improved message handling and user identification. The adapter now resolves immediately after successful launch instead of running indefinitely, providing better bot lifecycle control. Enhanced error logging for improved diagnostics of post-launch failures.
  • Tests

    • Comprehensive test updates to validate adapter functionality and startup behavior with the new implementation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29b4917c-2041-48bb-94bf-fedf4e289edd

📥 Commits

Reviewing files that changed from the base of the PR and between 2732288 and a5e5d5c.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • packages/adapters/package.json
  • packages/adapters/src/chat/telegram/adapter.test.ts
  • packages/adapters/src/chat/telegram/adapter.ts

📝 Walkthrough

Walkthrough

The Telegram adapter is migrated from the Telegraf SDK to the grammY SDK. Dependencies are updated, test assertions adjusted for the new library API, and the main adapter implementation refactored to use grammY's bot type, API methods, and startup flow.

Changes

Cohort / File(s) Summary
Dependency Migration
packages/adapters/package.json
Removed telegraf (^4.16.0) and added grammy (^1.36.0) as runtime dependency.
Test Updates
packages/adapters/src/chat/telegram/adapter.test.ts
Updated test stubs and assertions to use grammY API; replaced bot.telegram with bot.api, updated type imports from telegraf.Context to grammy.Context, refactored start() mocks from launch to start pattern, and added stop() test.
Adapter Implementation
packages/adapters/src/chat/telegram/adapter.ts
Migrated bot type from Telegraf to Bot, replaced send calls from bot.telegram.sendMessage() to bot.api.sendMessage(), changed event handler from 'message' to 'message:text', updated startup from launch() to start() with callback, and added optional chaining for user ID extraction and error logging.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 From Telegraf's telegram to grammY's gleaming call,
Our Bun-bound bunny hops past readonly walls,
The adapter now starts, no launch delays in sight,
Message handlers dance with \api.sendMessage\\ delight! 🎉

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch archon/task-fix-issue-1042

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coleam00
Copy link
Copy Markdown
Owner Author

🔍 Comprehensive PR Review

PR: #1066 — fix: replace Telegraf with grammY to fix Bun TypeError crash
Reviewed by: 3 specialized agents (code-review, error-handling, test-coverage)
Date: 2026-04-10


Summary

This PR correctly migrates the Telegram adapter from Telegraf to grammY. All critical paths are accurate: the bot.start() / onStart Promise wrapper correctly handles grammY's "resolves on stop" semantics, the 409 retry logic is fully preserved, and test mocks are updated to model the never-resolving-until-stop behavior. One medium-severity issue was found (misleading log output), plus two low-severity quality observations and two minor test gaps.

Verdict: APPROVE — with a suggested 1-line fix before merge

Severity Count
🔴 CRITICAL 0
🟠 HIGH 0
🟡 MEDIUM 1
🟢 LOW 3

🟡 Medium Issue — Misleading log when userId is undefined

📍 packages/adapters/src/chat/telegram/adapter.ts:179

When ctx.from?.id is undefined (channel posts, service messages), String(undefined).slice(0, 4) produces "unde", so the log shows maskedUserId: "unde***". This is indistinguishable from a real masked user ID in whitelist mode and will confuse anyone triaging auth logs. Both Slack and Discord adapters already use an explicit undefined guard.

1-line fix:

// Before:
const maskedId = `${String(userId).slice(0, 4)}***`;

// After (matches Slack/Discord pattern):
const maskedId = userId !== undefined ? `${String(userId).slice(0, 4)}***` : 'unknown';

🟢 Low Issues

View 3 low-priority suggestions

1. Post-startup bot crash is silently dropped

📍 packages/adapters/src/chat/telegram/adapter.ts:202-213

After onStart fires and resolves the outer Promise, any subsequent grammY polling loop crash calls reject() on an already-settled Promise — a no-op. Adding one log line makes post-startup crashes visible:

.catch((err: unknown) => {
  const error = err instanceof Error ? err : new Error(String(err));
  getLog().error({ err: error }, 'telegram.bot_runtime_error'); // add this
  reject(error);
});

2. Message silently dropped when messageHandler is null

📍 packages/adapters/src/chat/telegram/adapter.ts:184-188

Same pattern as Slack/Discord (not a regression), but a debug-level log in the else branch would surface this during development at zero production overhead:

} else {
  getLog().debug({ chatId: ctx.chat?.id }, 'telegram.message_dropped_no_handler');
}

3. stop() has no test coverage (test-coverage agent, rating 5/10)

📍 packages/adapters/src/chat/telegram/adapter.ts:235-238

A 3-line test for stop()bot.stop() would guard against rename regressions introduced by the library swap.


✅ What's Good

  • bot.start() / onStart Promise wrapper is the correct minimal solution for grammY's "resolves on stop" semantics — clean and well-commented
  • message:text filter upgrade from manual 'text' in ctx.message guard to grammY-native filter is the idiomatic improvement
  • 409 retry logic fully preserved with warn logs on each attempt
  • ESLint prefer-promise-reject-errors compliance correctly handled
  • Test mocks correctly updated — the never-resolving Promise + onStart callback pattern is the key correctness proof for the migration
  • bun.lock correctly reflects removal of telegraf, @telegraf/types, safe-compare, sandwich-stream, and related transitive deps
  • handlerTimeout: Infinity removal is correct — grammY has no such limit

Reviewed by Archon comprehensive-pr-review workflow
Artifacts: ~/.archon/workspaces/coleam00/Archon/artifacts/runs/eda5de20427cc42743e6f922ce74616f/review/

@coleam00
Copy link
Copy Markdown
Owner Author

Fix Report

All 4 review findings have been addressed in commit fd03d2d.

Fixed (4/4)

MEDIUM — Misleading "unde***" masked ID when userId is undefined
packages/adapters/src/chat/telegram/adapter.ts:179
Changed String(undefined).slice(0, 4) pattern to match the Slack/Discord adapter convention:

const maskedId = userId !== undefined ? `${String(userId).slice(0, 4)}***` : 'unknown';

LOW — Post-startup bot crash silently dropped
Added getLog().error({ err: error }, 'telegram.bot_runtime_error') before reject(error) in the .catch() handler so post-startup polling loop crashes are visible in logs.

LOW — Silent message drop when messageHandler is null
Added a debug-level log in the else branch ('telegram.message_dropped_no_handler'). Zero production overhead (debug off by default), surfaces the issue during development/misconfiguration.

LOW/Test — stop() has no test coverage
Added a stop() test suite that stubs bot.stop() and verifies it is called exactly once, guarding against grammY API rename regressions.

Validation

  • bun run type-check: PASS
  • bun run lint: PASS (0 warnings)
  • bun run test: PASS (0 failures)

@coleam00
Copy link
Copy Markdown
Owner Author

Archon PR Validation Report

Verdict: APPROVE

Summary

Clean migration from EOL Telegraf v4 to grammY, fixing a deterministic Bun crash where redactToken() assigns to a non-writable error.message. The fix is the only viable path — Telegraf v4 is end-of-life and the crash is in library internals. All existing behavior preserved, tests updated, no issues found.

Bug Confirmation

Claim Main Feature
Telegraf crashes on Bun startup (redactToken() → non-writable error.message) Confirmed Fixed (grammY uses native fetch, no error-mutation)
Crash is deterministic (every startup) Confirmed Fixed
Telegraf v4 is EOL, no upstream fix Confirmed N/A

Issues

No blocking issues found. Fix quality: 5/5.

What's Done Well

  • Correct root cause analysis and optimal fix strategy
  • Clean 1:1 API migration with no unnecessary additions
  • Well-designed start() Promise wrapper for grammY semantics
  • Opportunistic minor fixes (masked ID bug, no-handler debug log)

Validated by archon-validate-pr workflow

coleam00 and others added 2 commits April 16, 2026 09:15
Telegraf v4's internal `redactToken()` assigns to readonly `error.message`
properties, which crashes under Bun's strict ESM mode. Telegraf is EOL.

Changes:
- Replace `telegraf` dependency with `grammy` ^1.36.0
- Migrate adapter from Telegraf API to grammY API (Bot, bot.api, bot.start)
- Use grammY's `onStart` callback pattern for async polling launch
- Preserve 409 retry logic and all existing behavior
- Update test mocks from telegraf types to grammy types

Fixes #1042

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix misleading 'unde***' log when ctx.from is undefined; use 'unknown'
  to match the Slack/Discord adapter pattern
- Log post-startup bot runtime errors before reject() (no-op after
  onStart fires but errors are now visible in logs)
- Add debug log when message is dropped due to no handler registered
- Add stop() unit test to guard against grammY API rename regressions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coleam00 coleam00 force-pushed the archon/task-fix-issue-1042 branch from fd03d2d to a5e5d5c Compare April 16, 2026 14:15
@coleam00 coleam00 merged commit 64bdd30 into dev Apr 16, 2026
@coleam00 coleam00 deleted the archon/task-fix-issue-1042 branch April 16, 2026 14:16
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
…1042

fix: replace Telegraf with grammY to fix Bun TypeError crash
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.

Telegram adapter crashes on Bun: TypeError readonly property in Telegraf

1 participant