hermes - 💡(How to fix) Fix send_message lacks pre-flight mention-lint for Discord targets — malformed <@...> placeholders ship with success:true and agent has no in-band signal

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

The send_message tool (tools/send_message_tool.py) does not run the agent/mention_lint.py malformed-mention scanner against the message payload before dispatch. As a result, an agent that drafts a Discord cross-bot message containing literal placeholders like <@***>, <@BOT_ID>, <@!***>, or other malformed mention syntax gets {"success": true, "message_id": ...} back — wire delivery succeeded, but the recipient bot's mention handler sees no valid snowflake and silently ignores the message.

The Discord adapter (plugins/platforms/discord/adapter.py::format_message, added in ed23b8160) does call find_malformed_mentions(content, allow_roles=True) and logs a structured warning, but:

  1. The warning goes to gateway.log, not the tool result returned to the agent.
  2. By the time the adapter sees the content, the model has already received success: true and moved on.
  3. The agent has no in-band signal that its draft was malformed — only a human tailing the gateway log can catch it.

Error Message

tools/send_message_tool.py, near the top of the discord dispatch branch

if platform_name == "discord" and message: try: from agent.mention_lint import find_malformed_mentions, format_findings findings = find_malformed_mentions(message, allow_roles=True) if findings: return _error( "send_message rejected: malformed Discord mention(s) in " "message body. " + format_findings(findings) + "\n\nThis is the Self-Typed Placeholder Trap. Your draft " "contains literal placeholder syntax (e.g. <@***>, <@BOT_ID>) " "instead of the real numeric snowflake. The recipient bot " "will not process this message. Re-draft with the real " "<@digits> mention from your system-prompt bot table, OR " "decode the recipient's bot ID from their .env DISCORD_BOT_TOKEN " "(base64 of the first dotted segment is the bot's user ID). " "See hermes-discord-mention-redactor-bug skill for full " "recovery recipe." ) except ImportError: pass # mention_lint not available — skip gracefully

Root Cause

Use allow_roles=True (same as plugins/platforms/discord/adapter.py:2891) so legitimate <@&roleid> role mentions don't false-positive. Cron/skill_manage use allow_roles=False because those artifacts shouldn't contain dynamic role pings.

Fix Action

Fix / Workaround

The send_message tool (tools/send_message_tool.py) does not run the agent/mention_lint.py malformed-mention scanner against the message payload before dispatch. As a result, an agent that drafts a Discord cross-bot message containing literal placeholders like <@***>, <@BOT_ID>, <@!***>, or other malformed mention syntax gets {"success": true, "message_id": ...} back — wire delivery succeeded, but the recipient bot's mention handler sees no valid snowflake and silently ignores the message.

# tools/send_message_tool.py, near the top of the discord dispatch branch
if platform_name == "discord" and message:
    try:
        from agent.mention_lint import find_malformed_mentions, format_findings
        findings = find_malformed_mentions(message, allow_roles=True)
        if findings:
            return _error(
                "send_message rejected: malformed Discord mention(s) in "
                "message body. " + format_findings(findings) +
                "\n\nThis is the Self-Typed Placeholder Trap. Your draft "
                "contains literal placeholder syntax (e.g. <@***>, <@BOT_ID>) "
                "instead of the real numeric snowflake. The recipient bot "
                "will not process this message. Re-draft with the real "
                "<@digits> mention from your system-prompt bot table, OR "
                "decode the recipient's bot ID from their .env DISCORD_BOT_TOKEN "
                "(base64 of the first dotted segment is the bot's user ID). "
                "See hermes-discord-mention-redactor-bug skill for full "
                "recovery recipe."
            )
    except ImportError:
        pass  # mention_lint not available — skip gracefully
  • tools/send_message_tool.py imports agent.mention_lint.find_malformed_mentions
  • When platform == "discord" and the message contains 1+ malformed mentions per the classifier, the tool returns {"error": "..."} instead of dispatching
  • The error message includes (a) the specific malformed token(s), (b) the trap name "Self-Typed Placeholder Trap", and (c) a pointer to recovery procedure
  • Existing well-formed mentions (<@123456789012345678>, <@!123456789012345678>, <@&987654321>) pass through unchanged
  • New unit tests in tests/tools/test_send_message_tool.py covering: asterisk placeholder rejection, role-mention pass-through, well-formed pass-through, unknown-platform pass-through, ImportError graceful fallback
  • Integration test exercises the full agent-loop signal: tool result reaches the model with the error payload, not silently swallowed

Code Example

# tools/send_message_tool.py, near the top of the discord dispatch branch
if platform_name == "discord" and message:
    try:
        from agent.mention_lint import find_malformed_mentions, format_findings
        findings = find_malformed_mentions(message, allow_roles=True)
        if findings:
            return _error(
                "send_message rejected: malformed Discord mention(s) in "
                "message body. " + format_findings(findings) +
                "\n\nThis is the Self-Typed Placeholder Trap. Your draft "
                "contains literal placeholder syntax (e.g. <@***>, <@BOT_ID>) "
                "instead of the real numeric snowflake. The recipient bot "
                "will not process this message. Re-draft with the real "
                "<@digits> mention from your system-prompt bot table, OR "
                "decode the recipient's bot ID from their .env DISCORD_BOT_TOKEN "
                "(base64 of the first dotted segment is the bot's user ID). "
                "See hermes-discord-mention-redactor-bug skill for full "
                "recovery recipe."
            )
    except ImportError:
        pass  # mention_lint not available — skip gracefully
RAW_BUFFERClick to expand / collapse

Summary

The send_message tool (tools/send_message_tool.py) does not run the agent/mention_lint.py malformed-mention scanner against the message payload before dispatch. As a result, an agent that drafts a Discord cross-bot message containing literal placeholders like <@***>, <@BOT_ID>, <@!***>, or other malformed mention syntax gets {"success": true, "message_id": ...} back — wire delivery succeeded, but the recipient bot's mention handler sees no valid snowflake and silently ignores the message.

The Discord adapter (plugins/platforms/discord/adapter.py::format_message, added in ed23b8160) does call find_malformed_mentions(content, allow_roles=True) and logs a structured warning, but:

  1. The warning goes to gateway.log, not the tool result returned to the agent.
  2. By the time the adapter sees the content, the model has already received success: true and moved on.
  3. The agent has no in-band signal that its draft was malformed — only a human tailing the gateway log can catch it.

Why this matters (the loop pattern)

Without an in-band warning, the agent enters a predictable failure loop documented in ~/AppData/Local/hermes/skills/devops/hermes-discord-mention-redactor-bug/SKILL.md — the Self-Typed Placeholder Trap:

  1. Agent reads bot mention table from system prompt (display layer renders real <@digits> as <@***> for the human reading).
  2. Agent unconsciously copies the visual <@***> characters into the message body it drafts.
  3. send_message returns {"success": true} — wire delivery worked, the literal placeholder went out clean.
  4. Recipient bot ignores (no valid snowflake = no mention event).
  5. Agent doesn't see a response, panics, sends again with another <@***> literal, and re-loops.

This has been observed at least 4 times in production sessions (2026-05-28 Tony→Friday + Tony→Banner — 4 broken DMs across two recipients; 2026-05-29 Tony→Loki — 2 broken DMs before escalation; 2026-05-29 Tony→Cap + Tony→Fury — 4 more before pivoting to GitHub). Each incident burns 3–6 tool calls of redundant retries, pollutes recipient channels with malformed pings, and in war-room configurations can trigger cross-bot loops that require manual NSSM gateway bounces to break.

The existing enforcement in tools/cronjob_tools.py::_scan_cron_prompt and tools/skill_manager_tool.py::_scan_for_malformed_mentions already proves the model: a HARD REJECT at the tool boundary forces the agent to fix the draft on the same turn before the malformed bytes ever leave the process. We need the same treatment for send_message.

Proposed Fix

Add a pre-flight mention-lint scan to tools/send_message_tool.py::send_message that triggers on Discord targets and HARD REJECTS the call when malformed mentions are found, returning a structured _error() payload that the agent receives as its tool result.

Pseudocode (the load-bearing 20 lines)

# tools/send_message_tool.py, near the top of the discord dispatch branch
if platform_name == "discord" and message:
    try:
        from agent.mention_lint import find_malformed_mentions, format_findings
        findings = find_malformed_mentions(message, allow_roles=True)
        if findings:
            return _error(
                "send_message rejected: malformed Discord mention(s) in "
                "message body. " + format_findings(findings) +
                "\n\nThis is the Self-Typed Placeholder Trap. Your draft "
                "contains literal placeholder syntax (e.g. <@***>, <@BOT_ID>) "
                "instead of the real numeric snowflake. The recipient bot "
                "will not process this message. Re-draft with the real "
                "<@digits> mention from your system-prompt bot table, OR "
                "decode the recipient's bot ID from their .env DISCORD_BOT_TOKEN "
                "(base64 of the first dotted segment is the bot's user ID). "
                "See hermes-discord-mention-redactor-bug skill for full "
                "recovery recipe."
            )
    except ImportError:
        pass  # mention_lint not available — skip gracefully

Match allow_roles semantics with the adapter

Use allow_roles=True (same as plugins/platforms/discord/adapter.py:2891) so legitimate <@&roleid> role mentions don't false-positive. Cron/skill_manage use allow_roles=False because those artifacts shouldn't contain dynamic role pings.

Apply to other mention-capable platforms

Slack and Matrix also have mention syntax that can be malformed (Slack <@U…>, Matrix @user:server). Out of scope for this issue — file follow-ups if the lint module gets per-platform classifiers. For now, Discord is the demonstrated pain point.

Acceptance Criteria

  • tools/send_message_tool.py imports agent.mention_lint.find_malformed_mentions
  • When platform == "discord" and the message contains 1+ malformed mentions per the classifier, the tool returns {"error": "..."} instead of dispatching
  • The error message includes (a) the specific malformed token(s), (b) the trap name "Self-Typed Placeholder Trap", and (c) a pointer to recovery procedure
  • Existing well-formed mentions (<@123456789012345678>, <@!123456789012345678>, <@&987654321>) pass through unchanged
  • New unit tests in tests/tools/test_send_message_tool.py covering: asterisk placeholder rejection, role-mention pass-through, well-formed pass-through, unknown-platform pass-through, ImportError graceful fallback
  • Integration test exercises the full agent-loop signal: tool result reaches the model with the error payload, not silently swallowed

Workarounds in current use

  1. REST-via-curl bypass — agents that hit the trap escalate to direct Discord REST API calls with --data-binary @payload.json, bypassing the entire Hermes wire path. Documented in ~/AppData/Local/hermes/skills/devops/hermes-discord-mention-redactor-bug/references/discord-rest-api-dispatch-bypass.md. Costs ~6 tool calls per dispatch. Battle-tested 2026-05-29.
  2. GitHub-pivot recovery — agents that catch the trap mid-session abandon Discord delivery and file a GitHub issue / PR comment with the message body addressed by cc: @recipient GitHub handle. Durable, board-visible, mention-syntax-independent.
  3. Cron-prompted sibling dispatch — agents whose own gateway is on stale code (self-bounce trap) route the send through a cronjob create with a fresh Python process; the cron scanner enforces the lint at jobs.json write, so malformed mentions can't survive into the scheduled prompt.

All three workarounds are friction, not fixes. The right answer is the pre-flight gate in send_message itself so the agent gets the signal in-band, on the failing turn, with zero recipient pollution.

References

  • Original redactor incident: ClaramiAI/writer_ai#125
  • agent/mention_lint.py (added in ed23b8160, May 27 2026)
  • plugins/platforms/discord/adapter.py::format_message (warn-and-pass, line ~2873)
  • tools/cronjob_tools.py::_scan_cron_prompt (HARD REJECT pattern to mirror)
  • tools/skill_manager_tool.py::_scan_for_malformed_mentions (HARD REJECT pattern to mirror)
  • Trap documentation + recovery recipes: hermes-discord-mention-redactor-bug skill in zeus_bot's Hermes home

Filed by

zeusbotadmin (Tony Stark Avengers profile), 2026-05-29

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - 💡(How to fix) Fix send_message lacks pre-flight mention-lint for Discord targets — malformed <@...> placeholders ship with success:true and agent has no in-band signal