From 6cba9b221d77e2fb5abc825388602d8a41dd8c24 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Tue, 10 Mar 2026 10:55:19 -0500 Subject: [PATCH 1/3] fix: use catch-all action handler for merge_test_to_main prefix matching Chat SDK's onAction with a string argument uses exact match, not prefix match. Changed to a catch-all handler that filters with startsWith("merge_test_to_main:") so dynamic action IDs like "merge_test_to_main:recoupable/api" are matched correctly. Co-Authored-By: Claude Opus 4.6 --- .../__tests__/onMergeTestToMainAction.test.ts | 10 +++++----- lib/coding-agent/handlers/onMergeTestToMainAction.ts | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts index b76c6be1..8405b85f 100644 --- a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts +++ b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts @@ -19,10 +19,10 @@ function createMockBot() { } describe("registerOnMergeTestToMainAction", () => { - it("registers merge_test_to_main: action handler", () => { + it("registers catch-all action handler", () => { const bot = createMockBot(); registerOnMergeTestToMainAction(bot); - expect(bot.onAction).toHaveBeenCalledWith("merge_test_to_main:", expect.any(Function)); + expect(bot.onAction).toHaveBeenCalledWith(expect.any(Function)); }); it("merges test to main and posts success", async () => { @@ -30,7 +30,7 @@ describe("registerOnMergeTestToMainAction", () => { const bot = createMockBot(); registerOnMergeTestToMainAction(bot); - const handler = bot.onAction.mock.calls[0][1]; + const handler = bot.onAction.mock.calls[0][0]; const mockThread = { post: vi.fn() }; @@ -45,7 +45,7 @@ describe("registerOnMergeTestToMainAction", () => { const bot = createMockBot(); registerOnMergeTestToMainAction(bot); - const handler = bot.onAction.mock.calls[0][1]; + const handler = bot.onAction.mock.calls[0][0]; const mockThread = { post: vi.fn() }; @@ -61,7 +61,7 @@ describe("registerOnMergeTestToMainAction", () => { const bot = createMockBot(); registerOnMergeTestToMainAction(bot); - const handler = bot.onAction.mock.calls[0][1]; + const handler = bot.onAction.mock.calls[0][0]; const mockThread = { post: vi.fn() }; diff --git a/lib/coding-agent/handlers/onMergeTestToMainAction.ts b/lib/coding-agent/handlers/onMergeTestToMainAction.ts index ad4f0209..57932baa 100644 --- a/lib/coding-agent/handlers/onMergeTestToMainAction.ts +++ b/lib/coding-agent/handlers/onMergeTestToMainAction.ts @@ -9,7 +9,9 @@ import { parseMergeTestToMainActionId } from "../parseMergeTestToMainActionId"; * @param bot */ export function registerOnMergeTestToMainAction(bot: CodingAgentBot) { - bot.onAction("merge_test_to_main:", async event => { + bot.onAction(async event => { + if (!event.actionId.startsWith("merge_test_to_main:")) return; + const thread = event.thread; const repo = parseMergeTestToMainActionId(event.actionId); From bd7a409db1e666b8edc42faf5d9dfdd768f56ea1 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Tue, 10 Mar 2026 10:58:58 -0500 Subject: [PATCH 2/3] fix: fire-and-forget merge to avoid Slack 3-second timeout Slack requires interaction responses within 3 seconds. The mergeGithubBranch call was blocking the response. Changed to fire-and-forget pattern so the handler returns immediately. Co-Authored-By: Claude Opus 4.6 --- .../__tests__/onMergeTestToMainAction.test.ts | 7 +++++++ .../handlers/onMergeTestToMainAction.ts | 17 ++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts index 8405b85f..269068af 100644 --- a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts +++ b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts @@ -35,6 +35,10 @@ describe("registerOnMergeTestToMainAction", () => { const mockThread = { post: vi.fn() }; await handler({ thread: mockThread, actionId: "merge_test_to_main:recoupable/chat" }); + // Flush fire-and-forget .then() chain + await vi.waitFor(() => { + expect(mockThread.post).toHaveBeenCalled(); + }); expect(mockMergeGithubBranch).toHaveBeenCalledWith("recoupable/chat", "test", "main", "ghp_test"); expect(mockThread.post).toHaveBeenCalledWith("✅ Merged test → main for recoupable/chat."); @@ -50,6 +54,9 @@ describe("registerOnMergeTestToMainAction", () => { const mockThread = { post: vi.fn() }; await handler({ thread: mockThread, actionId: "merge_test_to_main:recoupable/api" }); + await vi.waitFor(() => { + expect(mockThread.post).toHaveBeenCalled(); + }); expect(mockThread.post).toHaveBeenCalledWith( "❌ Failed to merge test → main for recoupable/api: Merge conflict", diff --git a/lib/coding-agent/handlers/onMergeTestToMainAction.ts b/lib/coding-agent/handlers/onMergeTestToMainAction.ts index 57932baa..5b4e7f03 100644 --- a/lib/coding-agent/handlers/onMergeTestToMainAction.ts +++ b/lib/coding-agent/handlers/onMergeTestToMainAction.ts @@ -26,14 +26,13 @@ export function registerOnMergeTestToMainAction(bot: CodingAgentBot) { return; } - const result = await mergeGithubBranch(repo, "test", "main", token); - - if (result.ok === false) { - const { message } = result; - await thread.post(`❌ Failed to merge test → main for ${repo}: ${message}`); - return; - } - - await thread.post(`✅ Merged test → main for ${repo}.`); + // Fire-and-forget so the Slack interaction responds within 3 seconds + mergeGithubBranch(repo, "test", "main", token).then(async result => { + if (result.ok === false) { + await thread.post(`❌ Failed to merge test → main for ${repo}: ${result.message}`); + return; + } + await thread.post(`✅ Merged test → main for ${repo}.`); + }); }); } From c88e33439e71506ec3e2466f5fb8372092ec2421 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Tue, 10 Mar 2026 10:59:43 -0500 Subject: [PATCH 3/3] Revert "fix: fire-and-forget merge to avoid Slack 3-second timeout" This reverts commit bd7a409db1e666b8edc42faf5d9dfdd768f56ea1. --- .../__tests__/onMergeTestToMainAction.test.ts | 7 ------- .../handlers/onMergeTestToMainAction.ts | 17 +++++++++-------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts index 269068af..8405b85f 100644 --- a/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts +++ b/lib/coding-agent/__tests__/onMergeTestToMainAction.test.ts @@ -35,10 +35,6 @@ describe("registerOnMergeTestToMainAction", () => { const mockThread = { post: vi.fn() }; await handler({ thread: mockThread, actionId: "merge_test_to_main:recoupable/chat" }); - // Flush fire-and-forget .then() chain - await vi.waitFor(() => { - expect(mockThread.post).toHaveBeenCalled(); - }); expect(mockMergeGithubBranch).toHaveBeenCalledWith("recoupable/chat", "test", "main", "ghp_test"); expect(mockThread.post).toHaveBeenCalledWith("✅ Merged test → main for recoupable/chat."); @@ -54,9 +50,6 @@ describe("registerOnMergeTestToMainAction", () => { const mockThread = { post: vi.fn() }; await handler({ thread: mockThread, actionId: "merge_test_to_main:recoupable/api" }); - await vi.waitFor(() => { - expect(mockThread.post).toHaveBeenCalled(); - }); expect(mockThread.post).toHaveBeenCalledWith( "❌ Failed to merge test → main for recoupable/api: Merge conflict", diff --git a/lib/coding-agent/handlers/onMergeTestToMainAction.ts b/lib/coding-agent/handlers/onMergeTestToMainAction.ts index 5b4e7f03..57932baa 100644 --- a/lib/coding-agent/handlers/onMergeTestToMainAction.ts +++ b/lib/coding-agent/handlers/onMergeTestToMainAction.ts @@ -26,13 +26,14 @@ export function registerOnMergeTestToMainAction(bot: CodingAgentBot) { return; } - // Fire-and-forget so the Slack interaction responds within 3 seconds - mergeGithubBranch(repo, "test", "main", token).then(async result => { - if (result.ok === false) { - await thread.post(`❌ Failed to merge test → main for ${repo}: ${result.message}`); - return; - } - await thread.post(`✅ Merged test → main for ${repo}.`); - }); + const result = await mergeGithubBranch(repo, "test", "main", token); + + if (result.ok === false) { + const { message } = result; + await thread.post(`❌ Failed to merge test → main for ${repo}: ${message}`); + return; + } + + await thread.post(`✅ Merged test → main for ${repo}.`); }); }