Skip to content

fix(telegram): honor removeAckAfterReply for status reactions#68067

Merged
steipete merged 6 commits intoopenclaw:mainfrom
poiskgit:fix/telegram-remove-ack-after-reply
Apr 21, 2026
Merged

fix(telegram): honor removeAckAfterReply for status reactions#68067
steipete merged 6 commits intoopenclaw:mainfrom
poiskgit:fix/telegram-remove-ack-after-reply

Conversation

@poiskgit
Copy link
Copy Markdown
Contributor

@poiskgit poiskgit commented Apr 17, 2026

Summary

Fix Telegram so messages.removeAckAfterReply is honored when messages.statusReactions.enabled = true.

Problem

Telegram previously removed ack reactions only in the plain ack path.

When status reactions were enabled, the finalization flow stopped at setDone() and left the reaction on the message, even when
emoveAckAfterReply was set to rue.

Discord already handled this correctly, so behavior was inconsistent across channels.

Root cause

Telegram used a status reaction controller for the status-reaction path, but the finalize path never cleared the reaction after reply completion.

Change

In the Telegram finalize path:

  • keep statusReactionController.setDone()
  • when
    emoveAckAfterReply is enabled, wait briefly
  • clear the reaction explicitly with Telegram's native reaction API:

eactionApi(chatId, msg.message_id, [])

  • add targeted tests covering both enabled and disabled
    emoveAckAfterReply cases for Telegram status reactions

Result

Telegram now behaves as expected:

  • final reply is delivered
  • done reaction is shown briefly
  • reaction is removed automatically when
    emoveAckAfterReply = true

Validation

  • manually verified against a live Telegram bot after restart
  • added focused unit coverage for the Telegram dispatch path
  • ran pnpm exec vitest run extensions/telegram/src/bot-message-dispatch.test.ts
  • result: 83 tests passed

@openclaw-barnacle openclaw-barnacle Bot added channel: telegram Channel integration: telegram size: S labels Apr 17, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 17, 2026

Greptile Summary

This PR fixes Telegram so messages.removeAckAfterReply is honored when messages.statusReactions.enabled = true. Previously, the statusReactionController path only called setDone() at finalization without ever clearing the reaction; the fix chains a cleanup call (reactionApi(chatId, msgId, []) after a 1500 ms delay) onto the setDone() promise and adds two unit tests covering the enabled and disabled cases.

  • The .then(…).catch(…) structure means a setDone() rejection short-circuits the chain and the reaction removal never runs, leaving the status reaction on the message indefinitely even when removeAckAfterReply is true and the reply was delivered. Scoping .catch() to setDone() only (see inline comment) would make cleanup unconditional.

Confidence Score: 4/5

Safe to merge after addressing the error-handling concern; the core fix is correct and tests are solid.

The primary logic is correct and the new tests exercise both the enabled and disabled paths. The P2 concern (a setDone() failure silently swallows the reaction cleanup) is a real behavioral edge case worth fixing before merge, but it is limited to an error path and does not affect the happy path that was previously broken.

extensions/telegram/src/bot-message-dispatch.ts — the setDone() .then().catch() chain around lines 1005–1014

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/telegram/src/bot-message-dispatch.ts
Line: 1005-1014

Comment:
**`setDone()` rejection swallows reaction cleanup**

If `setDone()` rejects (e.g. a transient Telegram API error), the single `.catch()` at the end absorbs it and the `reactionApi(chatId, msg.message_id, [])` call is never reached. The status reaction stays on the message indefinitely even though a final reply was delivered and `removeAckAfterReply` is `true`. The plain-ack path avoids this because `removeAckReactionAfterReply` runs independently of the ack result.

Consider scoping `.catch()` only to `setDone()` so the cleanup always runs when `removeAckAfterReply` is enabled:

```suggestion
  if (statusReactionController) {
    void statusReactionController
      .setDone()
      .catch((err: unknown) => {
        logVerbose(`telegram: status reaction done failed: ${String(err)}`);
      })
      .then(async () => {
        if (!removeAckAfterReply) return;
        if (!msg.message_id || !reactionApi) return;
        await sleepWithAbort(1500);
        await reactionApi(chatId, msg.message_id, []);
      })
      .catch((err: unknown) => {
        logVerbose(`telegram: status reaction finalize failed: ${String(err)}`);
      });
  } else {
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(telegram): honor removeAckAfterReply..." | Re-trigger Greptile

Comment on lines +1005 to +1014
void Promise.resolve(statusReactionController.setDone())
.then(async () => {
if (!removeAckAfterReply) return;
if (!msg.message_id || !reactionApi) return;
await sleepWithAbort(1500);
await reactionApi(chatId, msg.message_id, []);
})
.catch((err: unknown) => {
logVerbose(`telegram: status reaction finalize failed: ${String(err)}`);
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 setDone() rejection swallows reaction cleanup

If setDone() rejects (e.g. a transient Telegram API error), the single .catch() at the end absorbs it and the reactionApi(chatId, msg.message_id, []) call is never reached. The status reaction stays on the message indefinitely even though a final reply was delivered and removeAckAfterReply is true. The plain-ack path avoids this because removeAckReactionAfterReply runs independently of the ack result.

Consider scoping .catch() only to setDone() so the cleanup always runs when removeAckAfterReply is enabled:

Suggested change
void Promise.resolve(statusReactionController.setDone())
.then(async () => {
if (!removeAckAfterReply) return;
if (!msg.message_id || !reactionApi) return;
await sleepWithAbort(1500);
await reactionApi(chatId, msg.message_id, []);
})
.catch((err: unknown) => {
logVerbose(`telegram: status reaction finalize failed: ${String(err)}`);
});
if (statusReactionController) {
void statusReactionController
.setDone()
.catch((err: unknown) => {
logVerbose(`telegram: status reaction done failed: ${String(err)}`);
})
.then(async () => {
if (!removeAckAfterReply) return;
if (!msg.message_id || !reactionApi) return;
await sleepWithAbort(1500);
await reactionApi(chatId, msg.message_id, []);
})
.catch((err: unknown) => {
logVerbose(`telegram: status reaction finalize failed: ${String(err)}`);
});
} else {
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/telegram/src/bot-message-dispatch.ts
Line: 1005-1014

Comment:
**`setDone()` rejection swallows reaction cleanup**

If `setDone()` rejects (e.g. a transient Telegram API error), the single `.catch()` at the end absorbs it and the `reactionApi(chatId, msg.message_id, [])` call is never reached. The status reaction stays on the message indefinitely even though a final reply was delivered and `removeAckAfterReply` is `true`. The plain-ack path avoids this because `removeAckReactionAfterReply` runs independently of the ack result.

Consider scoping `.catch()` only to `setDone()` so the cleanup always runs when `removeAckAfterReply` is enabled:

```suggestion
  if (statusReactionController) {
    void statusReactionController
      .setDone()
      .catch((err: unknown) => {
        logVerbose(`telegram: status reaction done failed: ${String(err)}`);
      })
      .then(async () => {
        if (!removeAckAfterReply) return;
        if (!msg.message_id || !reactionApi) return;
        await sleepWithAbort(1500);
        await reactionApi(chatId, msg.message_id, []);
      })
      .catch((err: unknown) => {
        logVerbose(`telegram: status reaction finalize failed: ${String(err)}`);
      });
  } else {
```

How can I resolve this? If you propose a fix, please make it concise.

@poiskgit
Copy link
Copy Markdown
Contributor Author

Addressed the Telegram error-path concern.

What changed:

  • scoped the setDone() failure handling so cleanup still runs when
    emoveAckAfterReply is enabled
  • kept a separate finalize-path catch for the delayed cleanup step

Validation:

  • ran pnpm exec vitest run extensions/telegram/src/bot-message-dispatch.test.ts
  • result: 83 tests passed

@poiskgit
Copy link
Copy Markdown
Contributor Author

Follow-up fix pushed for CI.

Reason:

  • setDone() is typed as �oid | Promise
  • chaining .catch() directly on it broke TypeScript in CI

Fix:

  • normalized the finalize path with Promise.resolve(statusReactionController.setDone())
  • preserved the same error-path behavior so cleanup still runs when setDone() fails

Validation:

  • reran pnpm exec vitest run extensions/telegram/src/bot-message-dispatch.test.ts
  • result: 83 tests passed

@poiskgit poiskgit force-pushed the fix/telegram-remove-ack-after-reply branch from 5005f83 to 1cb846b Compare April 18, 2026 14:03
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1cb846bde0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

.then(async () => {
if (!removeAckAfterReply) {return;}
if (!msg.message_id || !reactionApi) {return;}
await sleepWithAbort(1500);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use configured doneHoldMs for Telegram cleanup delay

The new status-reaction cleanup path hardcodes 1500 ms before clearing reactions, so messages.statusReactions.timing.doneHoldMs overrides are ignored on Telegram when removeAckAfterReply is enabled. In deployments that customize status-reaction timing (for faster or longer visibility), Telegram now behaves differently from the configured contract and from other channels that honor doneHoldMs.

Useful? React with 👍 / 👎.

@poiskgit
Copy link
Copy Markdown
Contributor Author

poiskgit commented Apr 18, 2026

Follow-up fix pushed to fully align Telegram status-reaction finalization with the existing timing contract.

What changed:

  • use configured doneHoldMs instead of a hardcoded 1500ms
  • honor errorHoldMs on Telegram finalize paths
  • restore the initial ack reaction when removeAckAfterReply is disabled
  • align success, error, and superseded finalize behavior behind a shared local helper
  • keep Telegram reaction clearing on reactionApi(chatId, msg.message_id, []) rather than controller.clear()

Validation:

  • ran pnpm exec vitest run extensions/telegram/src/bot-message-dispatch.test.ts
  • result: 95 tests passed

@steipete steipete force-pushed the fix/telegram-remove-ack-after-reply branch from 947fe51 to 52f98ec Compare April 21, 2026 00:39
@steipete steipete merged commit 32e8bca into openclaw:main Apr 21, 2026
91 checks passed
@steipete
Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Local gates: pnpm test extensions/telegram/src/bot-message-dispatch.test.ts (95 passed), pnpm check:changed (97 files / 1371 tests passed)
  • CI: green after rebased push
  • Land commit: 52f98ec
  • Squash merge commit: 32e8bca

Thanks @poiskgit!

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

Labels

channel: telegram Channel integration: telegram size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants