fix(slack): use unique action_ids for approval buttons

Slack requires unique action_id per button in a block. Changed from
single shared ID to per-action IDs (allow_once, allow_always, deny)
and use regex matching in the action handler.
This commit is contained in:
Kieran Klukas 2026-01-26 02:29:32 -05:00
parent 26cd21e73b
commit 07ae264704
No known key found for this signature in database
2 changed files with 42 additions and 34 deletions

View File

@ -8,7 +8,7 @@ import type { ExecApprovalDecision } from "../../infra/exec-approvals.js";
import { logDebug, logError } from "../../logger.js";
import type { RuntimeEnv } from "../../runtime.js";
const EXEC_APPROVAL_ACTION_ID = "clawdbot_execapproval";
const EXEC_APPROVAL_ACTION_ID_PREFIX = "clawdbot_execapproval";
const EXEC_APPROVAL_VALUE_PREFIX = "execapproval";
export type ExecApprovalRequest = {
@ -60,8 +60,12 @@ export function parseApprovalValue(
}
}
export function getExecApprovalActionId(): string {
return EXEC_APPROVAL_ACTION_ID;
export function getExecApprovalActionIdPrefix(): string {
return EXEC_APPROVAL_ACTION_ID_PREFIX;
}
export function matchesExecApprovalActionId(actionId: string): boolean {
return actionId.startsWith(EXEC_APPROVAL_ACTION_ID_PREFIX);
}
function formatApprovalBlocks(request: ExecApprovalRequest) {
@ -121,20 +125,20 @@ function formatApprovalBlocks(request: ExecApprovalRequest) {
{
type: "button",
text: { type: "plain_text", text: "✓ Allow once", emoji: true },
action_id: EXEC_APPROVAL_ACTION_ID,
action_id: `${EXEC_APPROVAL_ACTION_ID_PREFIX}_allow_once`,
value: encodeApprovalValue(request.id, "allow-once"),
style: "primary",
},
{
type: "button",
text: { type: "plain_text", text: "✓✓ Always allow", emoji: true },
action_id: EXEC_APPROVAL_ACTION_ID,
action_id: `${EXEC_APPROVAL_ACTION_ID_PREFIX}_allow_always`,
value: encodeApprovalValue(request.id, "allow-always"),
},
{
type: "button",
text: { type: "plain_text", text: "✗ Deny", emoji: true },
action_id: EXEC_APPROVAL_ACTION_ID,
action_id: `${EXEC_APPROVAL_ACTION_ID_PREFIX}_deny`,
value: encodeApprovalValue(request.id, "deny"),
style: "danger",
},

View File

@ -28,7 +28,8 @@ import { normalizeAllowList } from "./allow-list.js";
import type { MonitorSlackOpts } from "./types.js";
import {
SlackExecApprovalHandler,
getExecApprovalActionId,
getExecApprovalActionIdPrefix,
matchesExecApprovalActionId,
parseApprovalValue,
} from "./exec-approvals.js";
@ -234,43 +235,46 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
(
app as unknown as {
action: (
id: string,
id: string | RegExp,
handler: (args: {
ack: () => Promise<void>;
body: { user?: { id?: string } };
action: { value?: string };
action: { action_id?: string; value?: string };
respond: (payload: { text: string; response_type?: string }) => Promise<void>;
}) => Promise<void>,
) => void;
}
).action(getExecApprovalActionId(), async ({ ack, body, action, respond }) => {
await ack();
const parsed = parseApprovalValue(action?.value);
if (!parsed) {
).action(
new RegExp(`^${getExecApprovalActionIdPrefix()}_`),
async ({ ack, body, action, respond }) => {
await ack();
const parsed = parseApprovalValue(action?.value);
if (!parsed) {
await respond({
text: "This approval button is no longer valid.",
response_type: "ephemeral",
});
return;
}
const decisionLabel =
parsed.action === "allow-once"
? "Allowed (once)"
: parsed.action === "allow-always"
? "Allowed (always)"
: "Denied";
await respond({
text: "This approval button is no longer valid.",
text: `Submitting decision: **${decisionLabel}**...`,
response_type: "ephemeral",
});
return;
}
const decisionLabel =
parsed.action === "allow-once"
? "Allowed (once)"
: parsed.action === "allow-always"
? "Allowed (always)"
: "Denied";
await respond({
text: `Submitting decision: **${decisionLabel}**...`,
response_type: "ephemeral",
});
const ok = await execApprovalHandler!.resolveApproval(parsed.approvalId, parsed.action);
if (!ok) {
await respond({
text: "Failed to submit approval. The request may have expired or already been resolved.",
response_type: "ephemeral",
});
}
});
const ok = await execApprovalHandler!.resolveApproval(parsed.approvalId, parsed.action);
if (!ok) {
await respond({
text: "Failed to submit approval. The request may have expired or already been resolved.",
response_type: "ephemeral",
});
}
},
);
}
}