From 116f0d1e91178dd47cb5dd4601198d4f677c138e Mon Sep 17 00:00:00 2001 From: "blink-so[bot]" <211532188+blink-so[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:43:10 +0000 Subject: [PATCH] fix(slack): use z.coerce.string() for timestamp fields to handle LLM numeric inputs LLMs sometimes pass Slack timestamps as numbers instead of strings (e.g., 1234567890.123456 instead of "1234567890.123456"). The Slack API requires these to be strings, and without coercion, Zod validation would reject numeric timestamps causing slack_sendMessage and related tools to fail. This change uses z.coerce.string() for all timestamp fields (ts, thread_ts) to automatically convert numbers to strings, making the tools more robust to LLM output variations. Affected tools: - sendMessage (ts field) - reactToMessage (ts field) - readMessage (ts field) - readThreadReplies (ts field) - reportStatus (thread_ts field) --- packages/slack/src/tools.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/slack/src/tools.ts b/packages/slack/src/tools.ts index 28f6e43..c870330 100644 --- a/packages/slack/src/tools.ts +++ b/packages/slack/src/tools.ts @@ -47,7 +47,8 @@ export const createTools = ({ }: CreateToolsOptions) => { const sendMessageDescription = "The timestamp of the message to send in response to."; - let sendMessageTs = z + // Use coerce.string() to handle LLMs that pass timestamps as numbers + let sendMessageTs = z.coerce .string() .describe(sendMessageDescription) as unknown as z.ZodOptional; if (!disableMessagingInChannels) { @@ -170,7 +171,9 @@ ${formattingRules}` Prefer reacting to the most recent messages in a thread.`, inputSchema: z.object({ channel: z.string().describe("The channel to react to the message in."), - ts: z.string().describe("The timestamp of the message to react to."), + ts: z.coerce + .string() + .describe("The timestamp of the message to react to."), reaction: z.string().describe(`The Slack reaction to add to the message. Reactions: @@ -260,7 +263,7 @@ IMPORTANT: This MUST be text, not an emoji.`), "Read a message from a channel by ID. This reads attachments as well.", inputSchema: z.object({ channel: z.string().describe("The channel to read the message from."), - ts: z.string().describe("The timestamp of the message to read."), + ts: z.coerce.string().describe("The timestamp of the message to read."), }), execute: async (args) => { const messages = await client.conversations.history({ @@ -340,7 +343,7 @@ IMPORTANT: This MUST be text, not an emoji.`), channel: z .string() .describe("The channel to read the thread replies from."), - ts: z + ts: z.coerce .string() .describe( "The timestamp of the message to read the thread replies from." @@ -455,7 +458,7 @@ Clear the status by passing an empty string.`, "A short present-participle verb + brief user-facing update. NEVER use underscores or non-natural language words. Keep it short - under 100 characters." ), channel: z.string().describe("The channel to report your status to."), - thread_ts: z + thread_ts: z.coerce .string() .describe("The timestamp of the thread to report your status to."), }),