Skip to content

fix(discord): make clarify button labels always legible - #62291

Open
trevornk wants to merge 2 commits into
NousResearch:mainfrom
trevornk:fix/discord-clarify-button-label-readability
Open

fix(discord): make clarify button labels always legible#62291
trevornk wants to merge 2 commits into
NousResearch:mainfrom
trevornk:fix/discord-clarify-button-label-readability

Conversation

@trevornk

@trevornk trevornk commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Discord clarify buttons previously embedded truncated choice text directly in button labels. Mobile clients cut or wrap those labels aggressively, so even valid 80-character labels remain difficult to read.

Changes

  • Button labels are short numeric indexes.
  • Full numbered choice text appears in the embed field.
  • The same choices are mirrored in plain message content because some clients hide embeds.
  • The plain fallback now budgets the question, numbered choices, and instruction suffix against Discord's 2,000-character message-content limit.
  • Oversized choice text is truncated explicitly with [additional choice text truncated] while preserving the prompt and response instructions.
  • Existing dict-choice normalization remains intact.

Tests

Coverage verifies short button labels, full choice visibility, dict unwrapping, embed limits, and the 2,000-character plain-content cap with oversized choices.

pytest -q tests/gateway/test_discord_clarify_buttons.py
22 passed

git diff --check is clean.

@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins platform/discord Discord bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages P3 Low — cosmetic, nice to have labels Jul 10, 2026

@teknium1 teknium1 left a comment

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.

Thanks for addressing a real Discord clarify UX issue. Current main still builds button labels from choice text in plugins/platforms/discord/adapter.py:7552-7584, while Telegram already uses body text plus numeric labels at plugins/platforms/telegram/adapter.py:4689-4724.

Problems

  • plugins/platforms/discord/adapter.py:5806 in this diff truncates option_lines to 1024 characters, but the changed embed value appends a 65-character instruction suffix. A long option list can therefore create an 1089-character Choices field, above Discord's 1024-character cap, and send_clarify() will return a failed send after the caught API error.
  • This Discord-focused PR also carries unrelated changes in agent/chat_completion_helpers.py, hermes_cli/doctor.py, hermes_cli/plugins_cmd.py, plugins/memory/hindsight/__init__.py, and tools/lazy_deps.py.

Suggested changes

  • Cap the complete Choices-field value, reserving instruction text before truncating options, and add a boundary regression test asserting the field stays within 1024 characters.
  • Split the unrelated carried commits so the Discord fix can be reviewed and salvaged independently.

Automated hermes-sweeper review.

# which has a much higher cap (1024 for embed fields).
option_lines = "\n".join(
f"**{i + 1}.** {c}" for i, c in enumerate(clean_choices)
)

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.

option_lines is capped at 1024 here, but the next field value appends the 65-character instruction suffix. Cap the complete rendered field (or reserve suffix space first), otherwise a long option list exceeds Discord's 1024-character embed-field limit and the prompt send fails.

Discord clarify buttons put the (truncated) choice text directly on
the button label, relying on Discord's 80-char label cap plus a
word-boundary truncation algorithm. In practice this is still
unreadable: Discord mobile clients wrap/cut button text well before
80 chars (often <40 visible), so any truncation strategy on the
label itself still garbles longer choices regardless of how the cut
point is chosen.

PR NousResearch#54969 (merged as 87be36c) already fixed a related but
different bug in this same truncation path -- code-point slicing
instead of UTF-16 unit slicing, which could corrupt emoji-heavy
choice text at the cut boundary. That fix made the truncation
correct; this one removes the need for truncation at all.

Telegram and WhatsApp adapters already avoid this class of bug
entirely: button labels are just a short index, and the full choice
text is mirrored in the message body where there's no meaningful
length cap. This brings Discord's send_clarify() in line with that
same pattern:

- ClarifyChoiceView button labels are now just the option number
  ("1", "2", ...) instead of "1. <truncated text>". Removed the
  now-dead word/soft-boundary truncation logic for this path.
- send_clarify() renders the full, untruncated choice text as a
  numbered list in the embed's "Choices" field (1024-char cap,
  truncated only if genuinely absurd) and mirrors it again in the
  plain-text message content, matching the existing embeds-may-be-
  invisible-on-some-clients precaution already used elsewhere in
  this adapter.

Related: NousResearch#36186 (different bug in the same file -- clarify text
disappearing when Discord hides embed bodies entirely, not button
label truncation).

Review fixes (per hermes-sweeper automated review on PR NousResearch#62291):
- The embed field truncation only capped the option-list portion at
  1024 chars, then appended a ~65-char instruction suffix afterward --
  the combined value could reach ~1089 chars, over Discord's real
  1024-char embed-field limit, causing send_clarify() to fail on
  exactly the long-choice-list case this fix exists to handle. Now
  reserves suffix length before truncating so the combined field
  value never exceeds the cap. Added a boundary regression test with
  24 long choices asserting the rendered field stays <= 1024 chars
  and the suffix is never dropped.
- The plain-text message-content mirror now explicitly uses the
  *untruncated* option list (Discord's 2000-char content cap is much
  larger than the embed field's 1024), rather than reusing whatever
  the embed truncated to.
- Rebased onto a clean upstream main and dropped four unrelated
  fork-local commits (three explicitly tagged [carried], one
  untagged plugin symlink fix) that had accumulated on the source
  branch before this PR was cut -- this PR is Discord-only again.

Tests: tests/gateway/test_discord_clarify_buttons.py -- 5
truncation-behavior tests replaced with simpler "label is always
short" tests, 5 dict-unwrap tests updated to assert the full text
lands in the embed field rather than the button label, plus 1 new
boundary regression test for the field-cap fix. 21/21 pass. ruff
clean. git diff --check clean.
@trevornk
trevornk force-pushed the fix/discord-clarify-button-label-readability branch from bde33c0 to 7275c6b Compare July 11, 2026 12:35
@trevornk

Copy link
Copy Markdown
Contributor Author

Thanks for the catch — both points were real, fixed in the updated commit:

  1. Embed field cap bug: the truncation only bounded the option-list portion at 1024 chars, then appended the ~65-char instruction suffix afterward, so the combined value could hit ~1089 chars — over Discord's real 1024-char embed-field limit. Now reserves suffix length before truncating, so the combined field value never exceeds the cap. Added a boundary regression test (24 long choices) asserting the rendered field stays ≤1024 chars and the suffix is never dropped.

  2. Unrelated files: confirmed — the branch had picked up 4 fork-local commits before I cut it (three explicitly tagged [carried], one untagged plugin symlink fix) that had no business in a Discord-only PR. Rebased cleanly onto current upstream main and dropped all four; this PR is now a single commit touching only plugins/platforms/discord/adapter.py and its test file.

Verified: pytest tests/gateway/test_discord_clarify_buttons.py → 21/21 pass (20 existing + 1 new boundary test), ruff check clean, git diff --check clean.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 11, 2026
@trevornk

Copy link
Copy Markdown
Contributor Author

The inline review comment on option_lines = "\n".join(...) (line 5806) reflects an earlier, since-amended revision of this diff — the review's own diff_hunk snapshot shows no suffix-budget reservation, but the commit it's anchored to (7275c6b6) already contains the fix:

option_lines_full = "\n".join(
    f"**{i + 1}.** {c}" for i, c in enumerate(clean_choices)
)
embed_suffix = (
    "\n\nPick a button below, or click ✏️ Other to type a "
    "custom answer."
)
max_field = 1024
option_lines = option_lines_full
if len(option_lines) + len(embed_suffix) > max_field:
    budget = max_field - len(embed_suffix) - 3
    option_lines = option_lines_full[:budget] + "..."

Verified the arithmetic directly: with the 65-char suffix reserved before truncation, len(option_lines) + len(embed_suffix) is exactly 1024 in the worst case (long option list), never over. This has also been running live in production (deployed to a real Hermes gateway) since 2026-07-11 with no field-overflow failures. No code change needed — this looks like a comment left against a stale cached diff view rather than the commit's actual current content. Happy to push a no-op commit to force a fresh render if that would help, just let me know.

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This was generated by AI during triage.

Summary

Four PRs address the Discord clarify-choice readability problem: #62291 replaces clipped choice-text buttons with numeric buttons and mirrors numbered choices, #72606 and #72608 retain shortened text labels while adding full-choice lists, and #73545 conditionally adds a full-options field for choices exceeding a mobile-oriented threshold. The diffs differ mainly in overflow handling and UI semantics, with none fully handling every Discord text-surface limit as currently written.

Related pull requests

  • #62291 related — (+174/-133) — preferred, pending one boundary fix: replaces inherently clipped mobile labels with numeric buttons and mirrors normalized choices in the embed and plain content; the reviewed 1,024-character embed-field overflow and unrelated-file problems are resolved in the current diff, but the unbounded plain-text tail still needs a 2,000-character aggregate guard and regression test before merge.
  • #72606 [closed] related — (+216/-7) — superseded closed prototype: demonstrates the full-choice-list approach, but its cached diff includes unrelated custom-provider and TLS/header changes, and its 4,088-character description slice can silently discard later choices; it remains relevant as prior implementation evidence for #72608.
  • #72608 related — (+104/-10) — salvageable but not preferred: keeps truncated choice text on buttons and mirrors full choices into the embed and plain content, but the contributor review identifies two blocking diff-level defects—silent loss after the 4,088-character description slice and content potentially exceeding Discord's 2,000-character limit. Despite the keep_open review on #72608, consolidation onto #62291 is preferable because #62291 also fixes mobile button legibility rather than preserving clipped labels; the review's overflow concerns must still be carried into #62291 before merge.
  • #73545 related — (+26/-1) — incomplete alternative: minimally and conditionally adds a “Full options” field while preserving existing button semantics, but joins all normalized choices into an unbounded embed-field value with no 1,024-character guard or aggregate-overflow test, so valid long inputs can fail Discord validation.

Duplicates

#72606 and #72608 are substantially the same implementation lineage; #62291, #72608, and #73545 overlap on exposing numbered full-choice text but use competing UI and overflow strategies rather than being exact duplicates.

Suggested consolidation

Merge #62291 after bounding the complete plain-content mirror to Discord's 2,000-character limit with explicit overflow behavior and a boundary regression test. Then close #72608 and #73545 as superseded alternatives, explicitly carrying forward the keep_open review on #72608 rather than treating it as resolved; #72606 is already closed and remains superseded by its cleaned successor and the more complete #62291 design.

Cross-PR triage: Reviewed 4 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 46 kB of PR diffs, 8 kB of issue/PR text, 5 kB of discussion (7 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

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

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/discord Discord bot adapter sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants