diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7376b5c9b..3258a4734 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -130,6 +130,7 @@
- Dev templates: ship C-3PO dev workspace defaults as docs templates and use them for dev bootstrap. — thanks @steipete
- Config: fix Minimax hosted onboarding to write `agents.defaults` and allow `msteams` as a heartbeat target. — thanks @steipete
- Discord: add channel/category management actions (create/edit/move/delete + category removal). (#487) - thanks @NicholasSpisak
+- Discord: reject ambiguous bare numeric IDs in DM sends with clear guidance on `user:
-
+
`.
- To keep old “open to anyone” behavior: set `discord.dm.policy="open"` and `discord.dm.allowFrom=["*"]`.
diff --git a/docs/reference/AGENTS.default.md b/docs/reference/AGENTS.default.md
index cce2c4cda..957b516ab 100644
--- a/docs/reference/AGENTS.default.md
+++ b/docs/reference/AGENTS.default.md
@@ -92,7 +92,7 @@ git commit -m "Add Clawd workspace"
- **eightctl** — Control your sleep, from the terminal.
- **imsg** — Send, read, stream iMessage & SMS.
- **wacli** — WhatsApp CLI: sync, search, send.
-- **discord** — Discord actions: react, stickers, polls.
+- **discord** — Discord actions: react, stickers, polls. Use `user:` or `channel:` targets (bare numeric ids are ambiguous).
- **gog** — Google Suite CLI: Gmail, Calendar, Drive, Contacts.
- **spotify-player** — Terminal Spotify client to search/queue/control playback.
- **sag** — ElevenLabs speech with mac-style say UX; streams to speakers by default.
diff --git a/src/discord/send.test.ts b/src/discord/send.test.ts
index 6f714ca2b..9468d6d5d 100644
--- a/src/discord/send.test.ts
+++ b/src/discord/send.test.ts
@@ -109,6 +109,19 @@ describe("sendMessageDiscord", () => {
expect(res.channelId).toBe("chan1");
});
+ it("rejects bare numeric IDs as ambiguous", async () => {
+ const { rest } = makeRest();
+ await expect(
+ sendMessageDiscord("273512430271856640", "hello", { rest, token: "t" }),
+ ).rejects.toThrow(/Ambiguous Discord recipient/);
+ await expect(
+ sendMessageDiscord("273512430271856640", "hello", { rest, token: "t" }),
+ ).rejects.toThrow(/user:273512430271856640/);
+ await expect(
+ sendMessageDiscord("273512430271856640", "hello", { rest, token: "t" }),
+ ).rejects.toThrow(/channel:273512430271856640/);
+ });
+
it("adds missing permission hints on 50013", async () => {
const { rest, postMock, getMock } = makeRest();
const perms = PermissionFlagsBits.ViewChannel;
diff --git a/src/discord/send.ts b/src/discord/send.ts
index 8a4ff1395..d54811af7 100644
--- a/src/discord/send.ts
+++ b/src/discord/send.ts
@@ -305,6 +305,11 @@ function parseRecipient(raw: string): DiscordRecipient {
}
return { kind: "user", id: candidate };
}
+ if (/^\d+$/.test(trimmed)) {
+ throw new Error(
+ `Ambiguous Discord recipient "${trimmed}". Use "user:${trimmed}" for DMs or "channel:${trimmed}" for channel messages.`,
+ );
+ }
return { kind: "channel", id: trimmed };
}