# NousResearch/hermes-agent · Telegram typing indicator after inline approval

## Open now

- Nothing open on GitHub.

These members address a bug where the typing indicator does not resume after a user clicks an inline approval button in Telegram. A fix is proposed and referenced across the related pull requests and issue.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #28501. Verify it addresses your case.
- **Recommended:** read PR #28501 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #27853: Telegram inline approval buttons do not resume typing indicator after command approval

## Prior & competing PRs

- **#28501: fix(telegram): resume typing indicator after inline approval click (#27853)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `gateway/platforms/telegram.py`, `tests/gateway/test_telegram_approval_buttons.py`
  - fix hunks:
  - `gateway/platforms/telegram.py`

    ```diff
    @@ -2814,6 +2814,15 @@ async def _handle_callback_query(
                         )
                     except Exception as exc:
                         logger.error("Failed to resolve gateway approval from Telegram button: %s", exc)
    +                    count = 0
    +
    +                # Resume the typing indicator — paused when the approval was
    +                # sent (gateway/run.py).  The text /approve and /deny paths
    +                # call resume_typing_for_chat here too; without it, typing
    +                # stays paused for the rest of the turn after an inline
    +                # button click.
    +                if count and query_chat_id is not None:
    +                    self.resume_typing_for_chat(str(query_chat_id))
                 return
     
             # --- Slash-confirm callbacks (sc:choice:confirm_id) ---
    ```
  - `tests/gateway/test_telegram_approval_buttons.py`

    ```diff
    @@ -271,6 +271,67 @@ async def test_resolves_approval_on_click(self):
             # State should be cleaned up
             assert 1 not in adapter._approval_state
     
    +    @pytest.mark.asyncio
    +    async def test_resume_typing_after_inline_approval(self):
    +        """Clicking an inline approval button must un-pause the chat's typing.
    +
    +        Regression for #27853: the text /approve path resumed typing, but the
    +        ea: callback path did not, so the typing indicator stayed gone for the
    +        rest of a long-running turn after a button click.
    +        """
    +        adapter = _make_adapter()
    +        adapter._approval_state[5] = "agent:main:telegram:group:12345:99"
    +        adapter.pause_typing_for_chat("12345")
    +        assert "12345" in adapter._typing_paused
    +
    +        query = AsyncMock()
    +        query.data = "ea:once:5"
    +        query.message = MagicMock()
    +        query.message.chat_id = 12345
    +        query.from_user = MagicMock()
    +        query.from_user.first_name = "Norbert"
    +        query.from_user.id = "12345"
    +        query.answer = AsyncMock()
    +        query.edit_message_text = AsyncMock()
    +
    +        update = MagicMock()
    +        update.callback_query = query
    +        context = MagicMock()
    +
    +        with patch.dict(os.environ, {"TELEGRAM_ALLOWED_USERS": "*"}, clear=False):
    +            with patch("tools.approval.resolve_gateway_approval", return_value=1):
    +                await adapter._handle_callback_query(update, context)
    +
    +        assert "12345" not in adapter._typing_paused
    +
    +    @pytest.mark.asyncio
    +    async def test_typing_stays_paused_when_resolve_returns_zero(self):
    +        """If resolve_gateway_approval reports 0 resolves, the agent thread
    +        was never unblocked, so typing should NOT be force-resumed."""
    +        adapter = _make_adapter()
    +        adapter._approval_state[6] = "agent:main:telegram:group:12345:99"
    +        adapter.pause_typing_for_chat("12345")
    +
    +        query = AsyncMock()
    +        query.data = "ea:once:6"
    +        query.message = MagicMock()
    +        query.message.chat_id = 12345
    +        query.from_user = MagicMock()
    +        query.from_user.first_name = "Norbert"
    +        query.from_user.id = "12345"
    +        query.answer = AsyncMock()
    +        query.edit_message_text = AsyncMock()
    +
    +        update = MagicMock()
    +        update.callback_query = query
    +        context = MagicMock()
    +
    +        with patch.dict(os.environ, {"TELEGRAM_ALLOWED_USERS": "*"}, clear=False):
    +            with patch("tools.approval.resolve_gateway_approval", return_value=0):
    +                await adapter._handle_callback_query(update, context)
    +
    +        assert "12345" in adapter._typing_paused
    +
         @pytest.mark.asyncio
         async def test_approval_callback_escapes_dynamic_user_name(self):
             adapter = _make_adapter()
    ```
- **#27874: fix(telegram): resume typing indicator after inline approval click (#27853)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#28501`

## Fix status

- Merged to main in PR #28501 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-19`.
- Subsystem: `gateway`
