Skip to content

Commit bbfb26e

Browse files
committed
fix(slack): truncate oversized tool input in approval blocks
The HITL approval block serialized the tool input without a bound into a Slack section text field, which is capped near 3000 characters. A large input made chat.postMessage fail with invalid_blocks so the approve and deny controls never appeared. The serialized input is now capped to keep the block within the limit.
1 parent 481f6c1 commit bbfb26e

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

packages/slack/src/index.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@ describe("slack channel", () => {
5050
expect(values).toContain("call-1::deny");
5151
});
5252

53+
it("renderInteraction truncates an oversized tool input to stay under the Slack block limit", () => {
54+
const c = slack({ id: "s-hitl-big", token: "t" });
55+
const msg = c.renderInteraction?.(
56+
[
57+
{
58+
toolCallId: "call-big",
59+
toolName: "requestApproval",
60+
input: { blob: "x".repeat(10_000) },
61+
},
62+
],
63+
{ event: messageEvent(), deliveryId: "d1" }
64+
);
65+
expect(msg).not.toBeNull();
66+
const section = (msg!.blocks as any[]).find((b) => b.type === "section");
67+
const text = section.text.text as string;
68+
expect(text).toContain("... (truncated)");
69+
expect(text.length).toBeLessThan(3000);
70+
});
71+
5372
it("onInteraction resolves a block_actions click to a tool output; ignores messages", () => {
5473
const c = slack({ id: "s-hitl2", token: "t" });
5574
const approve = c.onInteraction?.({

packages/slack/src/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ export function slack<TEvent = SlackMessageEvent>(
188188
});
189189
}
190190

191+
/**
192+
* Cap for the serialized tool input in an approval block. A Slack section `text` field accepts about
193+
* 3000 characters; staying well under keeps a large input from failing chat.postMessage with
194+
* `invalid_blocks` and dropping the approval controls.
195+
*/
196+
const MAX_INTERACTION_INPUT_CHARS = 2500;
197+
191198
/**
192199
* Default HITL controls: render each pending human-decision tool as a Block Kit approve/deny pair. The
193200
* button `value` carries `${toolCallId}::${decision}` so `onInteraction` can resolve the exact tool.
@@ -198,7 +205,15 @@ function defaultSlackRenderInteraction(
198205
): ChannelMessage | null {
199206
const call = pending[0];
200207
if (!call) return null;
201-
const detail = call.input !== undefined ? "\n```" + safeStringify(call.input) + "```" : "";
208+
let detail = "";
209+
if (call.input !== undefined) {
210+
const serialized = safeStringify(call.input);
211+
const shown =
212+
serialized.length > MAX_INTERACTION_INPUT_CHARS
213+
? serialized.slice(0, MAX_INTERACTION_INPUT_CHARS) + "\n... (truncated)"
214+
: serialized;
215+
detail = "\n```" + shown + "```";
216+
}
202217
return {
203218
text: `Approval needed: ${call.toolName}`,
204219
blocks: [

0 commit comments

Comments
 (0)