Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

bug: Webhook requests repeatedly register middleware on cached bots until the call stack overflows

Aperta
#5 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
78/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
typescript
Ambito
api, backend

Direzione di ricerca

Start in start.ts at the webhook handler and then inspect src/bots.ts at getBot and the bot cache. Reproduce the repeated registration with the minimal middleware example from the issue, then verify that webhook requests reuse a configured bot without growing its middleware chain and that a terminating middleware completes normally.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

[!NOTE]
This issue was primarily written by GPT-5.6 Sol xhigh in ChatGPT Work.

Summary

In webhook mode, JsonBot registers the same application composer on the bot for every incoming POST request:

const bot = getBot(botMode, botToken);
if (bot) {
    bot.use(composer);
    return await webhookCallback(bot, "std/http")(req);
}

Source: https://github.com/SpEcHiDe/JsonBot/blob/3770d5cdcb40fe1ac8771d344eb5a1a04e5eca50/start.ts#L38-L46

However, getBot caches and returns the same Bot instance for subsequent requests using the same token:

const bots = new Map<string, Bot<MyContext>>();

export function getBot(mode: string, token: string) {
    let bot = bots.get(token);
    if (!bot) {
        // Construct and configure the bot
        // ...
        bots.set(token, bot);
    }
    return bot;
}

Source: https://github.com/SpEcHiDe/JsonBot/blob/3770d5cdcb40fe1ac8771d344eb5a1a04e5eca50/src/bots.ts#L4-L8
Source: https://github.com/SpEcHiDe/JsonBot/blob/3770d5cdcb40fe1ac8771d344eb5a1a04e5eca50/src/bots.ts#L59-L65

Consequently, every webhook request permanently appends another copy of the application composer to the cached bot's middleware chain. The chain grows without bound until dispatching an update throws:

RangeError: Maximum call stack size exceeded
    at .../src/composer.ts:147:12
    at .../src/composer.ts:149:15
    at .../src/composer.ts:149:15
    ...

The repeated composer.ts:149 frames in the reported stack trace are consistent with grammY recursively entering the increasingly deep chain of composed middleware.

Minimal reproduction

The failure still occurs when the registered application composer contains only a terminating leaf middleware. The leaf does not call next and therefore cannot itself recurse into downstream middleware:

import { Composer } from "grammy/mod.ts";

let leafCalls = 0;

const applicationComposer = new Composer();

// This is a terminating leaf. It does not accept or call `next`.
applicationComposer.use(() => {
    leafCalls++;
});

// Represents the Bot instance retained in JsonBot's `bots` map.
// Bot extends Composer, so the relevant composition behavior is identical.
const cachedBot = new Composer();

// Represents repeatedly executing `bot.use(composer)` inside the
// webhook request handler.
for (let request = 0; request < 20_000; request++) {
    cachedBot.use(applicationComposer);
}

try {
    await cachedBot.middleware()(
        {} as never,
        () => Promise.resolve(),
    );
} catch (error) {
    console.log(error instanceof RangeError); // true
    console.log(leafCalls); // 0
}

This produces RangeError: Maximum call stack size exceeded. The precise number of registrations required depends on the JavaScript engine and its available stack size.

Notably, leafCalls remains 0. The terminating application middleware is never reached before the stack overflows.

No Telegram API request or real context object is needed to reproduce the failure.

Registering applicationComposer once and then invoking the resulting middleware for 20,000 requests completes successfully.

Expected behavior

The application composer should be registered once when a bot is created. Processing additional webhook requests should not mutate or enlarge the bot's middleware chain.

A terminating application composer should then handle an update and stop the middleware flow normally.

Actual behavior

Every webhook request registers the application composer again on the cached bot.

The middleware chain and its retained closures therefore grow with the number of requests. After enough requests, middleware dispatch exceeds the maximum call-stack size.

Once this happens, later requests for the same cached bot continue to fail because the oversized middleware chain remains attached and continues growing.

The stack overflow occurs before execution reaches even the first terminating application middleware.

Suspected cause

Composer.use appends middleware by replacing the current handler with a new handler that wraps the complete previous handler:

use(...middleware: Array<Middleware<C>>) {
    const composer = new Composer(...middleware);
    this.handler = concat(this.handler, flatten(composer));
    return composer;
}

Source: https://github.com/grammyjs/grammY/blob/f9f7578d82ef127507aeb6902de8537b02ac994e/src/composer.ts#L227-L231

Conceptually, repeated registration produces:

H₁ = concat(H₀, applicationComposer)
H₂ = concat(H₁, applicationComposer)
H₃ = concat(H₂, applicationComposer)
...
Hₙ = concat(Hₙ₋₁, applicationComposer)

When Hₙ handles an update, concat invokes its first handler before it can invoke andThen:

return async (ctx, next) => {
    let nextCalled = false;
    await first(ctx, async () => {
        if (nextCalled) throw new Error("`next` already called before!");
        else nextCalled = true;
        await andThen(ctx, next);
    });
};

Source: https://github.com/grammyjs/grammY/blob/f9f7578d82ef127507aeb6902de8537b02ac994e/src/composer.ts#L143-L154

Execution must therefore descend through the accumulated outer handlers:

Hₙ → Hₙ₋₁ → Hₙ₋₂ → ... → H₀

Only after that descent could the first registered copy of applicationComposer run and terminate the middleware flow. With enough registrations, the call stack is exhausted during the descent, before that leaf is reached.

The terminating nature of the application composer prevents later middleware from running, but it does not prevent the cached bot's repeatedly wrapped outer handler from overflowing the stack.

The bot.use(composer) call itself does not normally throw immediately. The overflow occurs when grammY subsequently dispatches an update through the accumulated middleware chain.

The newer grammY revision currently pinned by JsonBot retains the same relevant Composer.use and concat implementation, so updating the grammY revision alone does not resolve this application lifecycle problem.

Suggested fix

The application composer should be registered exactly once when each bot is created.

Webhook requests should retrieve the already-configured bot and dispatch the update without mutating its middleware chain.

Lingua principale
TypeScript
Stelle
27
Fork
8
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

  • Include un Dockerfile o un file Docker Compose
  • Nessun modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.