Skip to content

"↩️ Answered:" confirmation truncated to 100 chars after AskUserQuestion text reply (agent receives full text) #528

@nathanschram

Description

Summary

When Claude Code calls AskUserQuestion, the user clicks "Other (type it out)" and replies with text, the bot's "↩️ Answered: …" confirmation message in Telegram is truncated to exactly 100 characters with no ellipsis. The agent receives the full untruncated text — the bug is display-only.

Reported by user on @hetz_lba1_bot (v0.35.3rc, lba-1 staging), affects all prior versions back to when this code path was introduced.

Reproduction

  1. On any Claude Code chat with @untether_dev_bot or @hetz_lba1_bot, trigger an AskUserQuestion call (or just send a question the agent will ask back).
  2. Click the "Other (type it out)" option (or just reply with text if ask mode is configured for text replies).
  3. Type a reply longer than 100 characters (e.g. "You tell me, please - please list all of the next tasks we can begin implementing/running now here in the chat").
  4. Observe the bot's confirmation: ↩️ Answered: You tell me, please - please list all of the next tasks we can begin i — cut off mid-word, no ellipsis.

Screenshot evidence (from reporter): user's typed reply "… now here in the chat" vs the "Answered:" echo which ends at "… begin i".

Impact

  • Cosmetic / UX only. Agent still receives the complete reply and responds correctly.
  • User has no visual confirmation that the full message was sent — looks like Untether dropped half their reply.
  • Affects all engines that use Untether's AskUserQuestion flow (Claude Code today; any future engine wired through this path).

Root cause

Two hardcoded text[:100] slices in src/untether/telegram/loop.py:

  • loop.py:2378 — multi-question / options flow (final answer reply):
    await reply(text=f"↩️ Answered: {text[:100]}")
  • loop.py:2391 — single-question flow:
    await reply(text=f"↩️ Answered: {text[:100]}")

No constant, no ellipsis, no word-boundary handling.

Agent path is unaffected (full text reaches Claude)

For reviewer reference — the full reply IS sent to the agent:

  • loop.py:2357 stores the full text in flow.answers[question_key] (multi-question flow).
  • loop.py:2389 passes the full text to answer_ask_question(ask_req_id, text).
  • runners/claude.py:3248-3263 (answer_ask_question) embeds the complete answer in deny_message.
  • runners/claude.py:3282-3297 (answer_ask_question_with_options) ships the full flow.answers dict via updatedInput.
  • runners/claude.py:1917-1953 (write_control_response) writes the untruncated JSONL line to Claude Code's stdin.

Proposed fix

Drop the [:100] slice on both lines — these confirmations are ephemeral courtesy echoes and shouldn't surprise the user by hiding their input. Telegram's 4096-char per-message limit is well above any reasonable typed reply, and render_markdown() / split_markdown_body() already exist for the rare overflow case.

# loop.py:2378 and :2391
await reply(text=f"↩️ Answered: {text}")

If keeping a short echo is preferred (e.g. for groups where mostly-blank confirmations clutter the timeline), use an ellipsis and a larger bound:

echo = text if len(text) <= 300 else text[:297] + "…"
await reply(text=f"↩️ Answered: {echo}")

Regression test

tests/test_ask_user_question.py covers the deny-message construction (agent path) but nothing exercises the loop.py confirmation path — which is why this slipped through. Add a test that:

  1. Registers a pending ask request and routes a reply >100 chars through the loop handler.
  2. Asserts the FakeTransport sent log contains the full reply text in the "↩️ Answered:" message.

Reference .claude/rules/testing-conventions.md for stub patterns (FakeTransport, anyio, etc.).

Affected files

  • src/untether/telegram/loop.py (lines 2378, 2391)
  • tests/test_ask_user_question.py (or new test file targeting the loop path)
  • CHANGELOG.md (v0.35.4 entry, ### fixes subsection with this issue link)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: lowseverity:minorSmall UX gap, edge case, cosmetic issue; doesn't block any workflow

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions