You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In an AskUserQuestion multi-question sequential flow, after the user clicks an option for the final question, the inline keyboard on that question message is not removed. Buttons remain visible and clickable. The structured answer DOES reach Claude correctly (control_response.sent approved=True) and Claude continues the task; the bug is purely a UX cleanup gap.
Subsequent clicks on the now-stale buttons fire ask_question.flow_missing action=opt warnings (no-op — flow state already cleaned up on the success path).
Reproduction (nsd VPS, v0.35.3rc17, 2026-05-17)
Host: nsd
Chat: -1003933918343 (NSD Marketing group)
Project: nsd-marketing
Session:edd9e51e-e62a-4e68-aa64-39e4a538bbc7
user_msg_id: 350
request_id:63a75c8f-c1ca-4ba6-821c-02a5a69b0ba7
Claude issued an AskUserQuestion with 2 sequential questions. User clicked option buttons on Q1 then Q2:
Compare to the "more questions" branch directly above (lines 124-136), which correctly calls await ctx.executor.edit(ctx.message, msg) to transition the message from Q1 → Q2 (which re-renders with fresh buttons). The final-answer branch sends a new "Answers sent: …" message via CommandResult.text but never touches the original question message — so its inline keyboard persists.
Impact
Severity: minor. The flow works; user just sees stale buttons.
User confusion: "did my answer go through?" — leads to redundant clicks (firing ask_question.flow_missing warnings).
Visual debt: Cleanup is asymmetric between the intermediate-question transition (clean) and the final-question completion (stale buttons).
In the final-answer branch (after success = await answer_ask_question_with_options(...)), strip the keyboard from the original message before constructing the CommandResult:
# After successful answer send, remove the keyboard from the question messageifsuccess:
cleared=RenderedMessage(
text=ctx.message.textorformat_question_message(flow), # preserve textextra={\"parse_mode\": \"HTML\", \"reply_markup\": {\"inline_keyboard\": []}},
)
try:
awaitctx.executor.edit(ctx.message, cleared)
exceptExceptionasexc: # noqa: BLE001logger.warning(\"ask_question.keyboard_clear_failed\", exc=str(exc))
# ... existing answer_lines / answers_summary code follows
Alternative: register the question message as ephemeral via register_ephemeral_message() in runner_bridge.py at the point the Ask flow is shown, so it's auto-deleted when the run completes via ProgressEdits.delete_ephemeral(). The register_ephemeral_message pattern is the standard Untether convention for approval-related messages (per .claude/rules/telegram-transport.md § Ephemeral messages).
Acceptance criteria
After the final question in a multi-Q AskUserQuestion flow is answered, the original question message no longer carries an inline keyboard.
The ask_question.flow_missing action=opt warning no longer fires from user re-clicks on a completed flow (because the buttons are gone).
Unit test in tests/test_ask_user_question.py covering: 2-question flow, click Q1 then Q2, assert ctx.executor.edit called with empty/cleared reply_markup on the final answer.
Symptom
In an AskUserQuestion multi-question sequential flow, after the user clicks an option for the final question, the inline keyboard on that question message is not removed. Buttons remain visible and clickable. The structured answer DOES reach Claude correctly (
control_response.sent approved=True) and Claude continues the task; the bug is purely a UX cleanup gap.Subsequent clicks on the now-stale buttons fire
ask_question.flow_missing action=optwarnings (no-op — flow state already cleaned up on the success path).Reproduction (nsd VPS, v0.35.3rc17, 2026-05-17)
edd9e51e-e62a-4e68-aa64-39e4a538bbc763a75c8f-c1ca-4ba6-821c-02a5a69b0ba7Claude issued an AskUserQuestion with 2 sequential questions. User clicked option buttons on Q1 then Q2:
After 12:51:09 the keyboard should be stripped from the original Q2 message. It isn't.
Root cause
src/untether/telegram/commands/ask_question.py:137-152— the "all questions answered" branch ofAskQuestionCommand.handle():Compare to the "more questions" branch directly above (lines 124-136), which correctly calls
await ctx.executor.edit(ctx.message, msg)to transition the message from Q1 → Q2 (which re-renders with fresh buttons). The final-answer branch sends a new "Answers sent: …" message viaCommandResult.textbut never touches the original question message — so its inline keyboard persists.Impact
ask_question.flow_missingwarnings).control_response.sentfired. Once the keyboard is removed promptly, this kind of repeated clicking should go away naturally.Fix sketch
In the final-answer branch (after
success = await answer_ask_question_with_options(...)), strip the keyboard from the original message before constructing theCommandResult:Alternative: register the question message as ephemeral via
register_ephemeral_message()inrunner_bridge.pyat the point the Ask flow is shown, so it's auto-deleted when the run completes viaProgressEdits.delete_ephemeral(). Theregister_ephemeral_messagepattern is the standard Untether convention for approval-related messages (per.claude/rules/telegram-transport.md§ Ephemeral messages).Acceptance criteria
ask_question.flow_missing action=optwarning no longer fires from user re-clicks on a completed flow (because the buttons are gone).tests/test_ask_user_question.pycovering: 2-question flow, click Q1 then Q2, assertctx.executor.editcalled with empty/clearedreply_markupon the final answer.Related
.claude/rules/telegram-transport.md§ Ephemeral messages (canonical cleanup pattern)Discovered: 2026-05-17 during direct user-reported observation (NSD Marketing chat). Logs cross-checked on host
nsdviajournalctl --user -u untether.Version: v0.35.3rc17 (all 4 fleet hosts).