Skip to content

getUpdates parser loses updates and silently swallows callback queries #137

Description

@rvalitov

Type: bug
Area: Telegram bot
Severity: high — silently loses user commands; blocks inline keyboards entirely
Addressed by: PR 2 of 4 (feat(telegram): keyboard primitives and update-parser rewrite)

Summary

Two related defects in the incoming-update path. Both are silent — no error is
logged in either case.

Bug A — callback_query updates are permanently swallowed

The handler does:

r.get('message', {}).get('text', '')

A callback_query update has no message key at that position, so this yields
an empty text and an empty chat id. The loop guard only checks the update id:

[ -z "$_uid" ] && continue

so _process_cmd runs on empty input, writes the offset, and Telegram is
told the update was consumed. The callback is never redelivered.

Impact: this is currently latent because nothing sends inline keyboards. It
becomes an immediate hard blocker for any inline-keyboard feature — every button
tap would be acknowledged away and do nothing.

Bug B — the no-python3 fallback drops updates

On a host without a working python3, the fallback extracts the text and the
chat id in two independent passes:

_text=$(echo "$updates" | grep -oE '"text"\s*:\s*"[^"]*"' | tail -1 | sed ...)
_cid=$(echo "$updates" | grep -oE '"chat"\s*:\s*\{[^}]*"id"\s*:\s*-?[0-9]+' | tail -1 | ...)

Then pairs them by position. Consequences:

  1. Every update but the last in a batch is dropped. Telegram delivers
    batched updates whenever more than one message arrives between polls (for
    example while the bot is restarting). Those commands vanish.
  2. Text and chat id can be paired across updates. In a two-update batch,
    update B's text can execute against update A's chat id.
  3. "chat"\s*:\s*\{ requires chat and { to be adjacent, so a reordered
    object fails to match even for a single update.
  4. The update offset is passed as the update_id argument to _process_cmd.

Impact: this is not a corner case. Alpine is a supported platform (OpenRC
support landed in #130) and typically ships without python3, so the fallback is
the only path there.

Reproduction (Bug B)

Send two messages to the bot in quick succession while it is between polls. Only
the second is processed. Note that command -v python3 is not a reliable guard:
the Windows Store ships a python3.exe alias that is on PATH and fails when
executed.

Proposal

Have both extractors emit the same normalised records:

<update_id>\t<kind>\t<chat_id>\t<message_id>\t<callback_id>\t<payload>

with kindmsg|cb, so a single consumer dispatches both. A record should be
emitted for every element of result — including ones carrying no message —
because the consumer advances the offset per record, and skipping one would make
Telegram redeliver it forever.

The fallback must be a character scanner, not a regex pass: a regex cannot
tell whether a } or " sits inside a string literal, and a command's text can
legitimately contain both.

Also:

  • Probe python3 by executing it, not with command -v.
  • Move the offset write out of _process_cmd into the consumer, so it advances
    per consumed record and a mid-batch failure redelivers the tail rather than
    losing it.
  • Read the records with process substitution rather than a pipe, so dispatchers
    run in the main shell.

Acceptance criteria

  • A batch of N updates yields N records, in order, with none dropped.
  • Each chat id is paired with its own text.
  • callback_query updates produce a cb record carrying the callback id,
    the message id and the payload.
  • Text containing "chat":{"id":999} does not corrupt parsing.
  • Both extractors produce byte-identical output on the same input.
  • Truncated JSON yields no records and no crash.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions