Skip to content

fix(discord): default bare numeric IDs to channel kind, classify ambiguous recipient as permanent error#30198

Closed
jini92 wants to merge 2 commits intoopenclaw:mainfrom
jini92:fix/discord-ambiguous-recipient
Closed

fix(discord): default bare numeric IDs to channel kind, classify ambiguous recipient as permanent error#30198
jini92 wants to merge 2 commits intoopenclaw:mainfrom
jini92:fix/discord-ambiguous-recipient

Conversation

@jini92
Copy link
Copy Markdown

@jini92 jini92 commented Mar 1, 2026

Summary

  • Problem: Bare numeric Discord IDs (e.g. 1466624220632059934) thrown as "Ambiguous Discord recipient" errors, causing messages to accumulate in the delivery queue with infinite retries.
  • Why it matters: Any send operation using a raw channel/user ID (common in cron tasks and programmatic sends) fails silently and fills up the queue, requiring manual purge.
  • What changed: (1) parseRecipient in send.shared.ts now defaults bare numeric IDs to channel kind instead of throwing ambiguous errors. (2) delivery-queue.ts classifies "ambiguous discord recipient" as a permanent error to stop retry loops if the error ever surfaces through another path.
  • What did NOT change: Behavior for explicitly prefixed targets (user:ID, channel:ID) is unchanged.

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • Integrations

Linked Issue/PR

  • Related: delivery queue retry buildup on ambiguous Discord recipient errors

User-visible / Behavior Changes

Bare numeric Discord IDs passed to --target or to: now resolve to a channel instead of erroring. Users who previously needed channel:ID prefix for programmatic sends can now use the raw ID.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Windows 11 (x64)
  • Runtime/container: Node 22, openclaw gateway
  • Integration/channel: Discord

Steps

  1. Configure a Discord send target as a bare numeric ID (e.g. in a cron job or skill using channel: "1466624220632059934")
  2. Trigger a send
  3. Observe delivery queue

Expected

  • Message delivered to the channel

Actual (before fix)

  • Ambiguous Discord recipient "1466624220632059934" error thrown
  • Message enters retry loop; queue accumulates

Evidence

  • Trace/log snippets

Before fix:

Error: Ambiguous Discord recipient "1466624220632059934". Use "user:..." for DMs or "channel:..." for channel messages.

After fix: message delivered without error.

Also cleared 7 accumulated messages from delivery queue after applying the fix.

Human Verification (required)

  • Verified scenarios: bare numeric channel ID, bare numeric DM ID, explicit channel:ID prefix (unchanged), explicit user:ID prefix (unchanged)
  • Edge cases checked: non-numeric bare strings still go through normal resolution
  • What you did not verify: voice channels, thread IDs as bare IDs

Compatibility / Migration

  • Backward compatible? Yes — explicit channel: / user: prefixes still work as before; only bare IDs change behavior (was error, now defaults to channel)
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • Revert: remove defaultKind: "channel" from send.shared.ts and the /ambiguous discord recipient/i entry from delivery-queue.ts
  • Known bad symptoms: if a bare ID was intended as a user DM but gets routed to a channel — add explicit user: prefix

Risks and Mitigations

  • Risk: bare numeric ID that is a user ID gets sent to a channel with the same numeric ID (if one exists)
    • Mitigation: channel resolution is attempted first; Discord user IDs and channel IDs share the same snowflake space but channels are looked up first in the guild cache. Users who need explicit DM sends should use user:ID.

Default bare numeric Discord IDs to channel kind instead of throwing
ambiguous recipient errors. Also classify ambiguous recipient as a
permanent delivery error to prevent infinite retry queue buildup.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@openclaw-barnacle openclaw-barnacle bot added channel: discord Channel integration: discord size: XS labels Mar 1, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64f52ff9f9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".


function parseRecipient(raw: string): DiscordRecipient {
const target = parseDiscordTarget(raw, {
defaultKind: "channel",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Apply bare-ID default in active recipient parser

Setting defaultKind: "channel" inside parseRecipient does not fix the outbound send flow, because the code paths that actually send messages (parseAndResolveRecipient callers in src/discord/send.outbound.ts) never use parseRecipient; they still construct parseOptions without defaultKind and then call parseDiscordTarget(raw, parseOptions), which continues to throw on bare numeric IDs. In practice, bare IDs still fail delivery, and with the new permanent-error classification they are now dropped immediately instead of retried, so the intended behavior change is still not applied where users send messages.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 1, 2026

Greptile Summary

This PR fixes the delivery-queue retry-loop caused by bare numeric Discord IDs throwing "Ambiguous Discord recipient" errors. The synchronous parseRecipient path is correctly patched with defaultKind: "channel", and the permanent-error safety net in delivery-queue.ts is a reasonable belt-and-suspenders addition. However, the async parseAndResolveRecipient function is not patched — it constructs parseOptions without defaultKind, so bare numeric IDs still throw the same ambiguous error when routed through that code path, leaving the original bug partially unresolved.

Key changes:

  • src/discord/send.shared.ts — adds defaultKind: "channel" to parseRecipient's parseOptions, defaulting bare numeric IDs to channel instead of throwing.
  • src/infra/outbound/delivery-queue.ts — adds /ambiguous discord recipient/i to PERMANENT_ERROR_PATTERNS so any remaining occurrences of this error move the entry to failed/ immediately instead of retrying forever.
  • parseAndResolveRecipient (also in send.shared.ts) still builds its parseOptions without defaultKind: "channel", meaning callers using the async resolution path (e.g., username-resolving sends) will still encounter the ambiguous-recipient throw for bare numeric IDs.

Confidence Score: 3/5

  • The PR partially fixes the issue but leaves the same ambiguous-recipient throw reachable through parseAndResolveRecipient, which was not updated.
  • The synchronous parseRecipient path is correctly fixed, and the delivery-queue.ts safety net is sound. However, parseAndResolveRecipient — the async counterpart used for username-resolved sends — still builds parseOptions without defaultKind, so bare numeric IDs through that path will still throw and accumulate in the delivery queue. The fix is incomplete, reducing confidence in full resolution of the stated problem.
  • src/discord/send.shared.ts — specifically parseAndResolveRecipient at lines 88–90, which needs the same defaultKind: "channel" addition applied to parseRecipient.

Last reviewed commit: 64f52ff

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 1, 2026

Additional Comments (1)

src/discord/send.shared.ts
parseAndResolveRecipient still throws for bare numeric IDs

The fix adds defaultKind: "channel" to the synchronous parseRecipient, but the async parseAndResolveRecipient (used for programmatic/username-resolved sends) builds its parseOptions without defaultKind. Tracing through resolveDiscordTarget in targets.ts:

  1. For a bare numeric ID like "1466624220632059934", safeParseDiscordTarget swallows the throw and returns undefined.
  2. isLikelyUsername returns false (the regex /[\d]+$/ matches pure numeric strings).
  3. shouldLookup is false.
  4. The function reaches return directParse ?? parseDiscordTarget(trimmed, parseOptions), where directParse is undefined, so it calls parseDiscordTarget with no defaultKind — which throws the ambiguous error.

Any caller that routes through parseAndResolveRecipient will still hit the retry-loop bug. The fix should be applied here as well:

  const parseOptions = {
    defaultKind: "channel" as const,
    ambiguousMessage: `Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
  };
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/discord/send.shared.ts
Line: 88-90

Comment:
**`parseAndResolveRecipient` still throws for bare numeric IDs**

The fix adds `defaultKind: "channel"` to the synchronous `parseRecipient`, but the async `parseAndResolveRecipient` (used for programmatic/username-resolved sends) builds its `parseOptions` without `defaultKind`. Tracing through `resolveDiscordTarget` in `targets.ts`:

1. For a bare numeric ID like `"1466624220632059934"`, `safeParseDiscordTarget` swallows the throw and returns `undefined`.
2. `isLikelyUsername` returns `false` (the regex `/[\d]+$/` matches pure numeric strings).
3. `shouldLookup` is `false`.
4. The function reaches `return directParse ?? parseDiscordTarget(trimmed, parseOptions)`, where `directParse` is `undefined`, so it calls `parseDiscordTarget` with **no `defaultKind`** — which **throws** the ambiguous error.

Any caller that routes through `parseAndResolveRecipient` will still hit the retry-loop bug. The fix should be applied here as well:

```suggestion
  const parseOptions = {
    defaultKind: "channel" as const,
    ambiguousMessage: `Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
  };
```

How can I resolve this? If you propose a fix, please make it concise.

@jini92
Copy link
Copy Markdown
Author

jini92 commented Mar 1, 2026

Good catch - applied the same defaultKind=channel fix to parseAndResolveRecipient (async path) as well. Pushed in 8f3a979.

@thewilloftheshadow
Copy link
Copy Markdown
Member

Superseded by #33142 which consolidates the fixes from this PR. Closing this in favor of that PR.

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

Labels

channel: discord Channel integration: discord size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants