From 604df8d10f66ca59ea2ab35131828f499a2f9693 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Jun 2026 03:18:01 +0000 Subject: [PATCH 1/2] Fix: fall back to default branch when workflow_dispatch trigger missing on PR branch When the router dispatches a workflow (e.g. skillet) on a PR's head branch and that branch doesn't have the compiled .lock.yml, GitHub returns HTTP 422 "Workflow does not have 'workflow_dispatch' trigger". The router now detects this error and retries on the default branch. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/route_slash_command.cjs | 44 ++++++++++++--- actions/setup/js/route_slash_command.test.cjs | 56 +++++++++++++++++++ 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs index 41c4ef267a0..114ae94d08c 100644 --- a/actions/setup/js/route_slash_command.cjs +++ b/actions/setup/js/route_slash_command.cjs @@ -274,9 +274,10 @@ async function addImmediateReaction(reaction) { * @param {string} workflowId * @param {string} ref * @param {Record} inputs + * @param {string} [fallbackRef] * @returns {Promise} */ -async function dispatchWorkflow(workflowId, ref, inputs) { +async function dispatchWorkflow(workflowId, ref, inputs, fallbackRef) { try { await github.rest.actions.createWorkflowDispatch({ owner: context.repo.owner, @@ -294,6 +295,10 @@ async function dispatchWorkflow(workflowId, ref, inputs) { core.info(`Skipping workflow '${workflowId}' because it is disabled.`); return false; } + if (isMissingWorkflowDispatchTriggerError(error) && fallbackRef && ref !== fallbackRef) { + core.info(`Workflow '${workflowId}' not found on ref '${ref}'; retrying on default branch '${fallbackRef}'.`); + return dispatchWorkflow(workflowId, fallbackRef, inputs); + } throw new Error(`Failed to dispatch workflow '${workflowId}' on ref '${ref}': ${String(error)}`); } } @@ -324,6 +329,20 @@ function isDisabledWorkflowDispatchError(error) { return message.includes("workflow is disabled") || message.includes("workflow was disabled") || message.includes("disabled workflow"); } +function isMissingWorkflowDispatchTriggerError(error) { + const status = error?.status ?? error?.response?.status; + const message = [error?.message, error?.response?.data?.message] + .filter(value => typeof value === "string" && value.trim()) + .join(" ") + .toLowerCase(); + + if (status !== 422 || !message) { + return false; + } + + return message.includes("does not have 'workflow_dispatch' trigger") || message.includes("no ref found for"); +} + /** * @param {Record>} slashRouteMap * @param {string} actualCommand @@ -367,6 +386,7 @@ async function main() { const identifier = eventIdentifier(); const { buildAwContext } = require("./aw_context.cjs"); const ref = await resolveDispatchRef(); + const defaultBranch = normalizeDispatchRef(context.payload?.repository?.default_branch || "main"); if (isPRClosedAtStart()) { core.info("Pull request is closed at workflow start; skipping centralized routing."); return; @@ -404,9 +424,14 @@ async function main() { ...(routeReaction ? { desired_ai_reaction: routeReaction } : {}), }; core.info(`Dispatching workflow '${workflowID}' for label '${labelName}'.`); - const dispatched = await dispatchWorkflow(workflowID, ref, { - aw_context: JSON.stringify(awContext), - }); + const dispatched = await dispatchWorkflow( + workflowID, + ref, + { + aw_context: JSON.stringify(awContext), + }, + defaultBranch + ); if (dispatched) { core.info(`Dispatched '${workflowID}' for label '${labelName}'`); } @@ -452,9 +477,14 @@ async function main() { ...(routeReaction ? { desired_ai_reaction: routeReaction } : {}), }; core.info(`Dispatching workflow '${workflowID}' for '/${commandName}'.`); - const dispatched = await dispatchWorkflow(workflowID, ref, { - aw_context: JSON.stringify(awContext), - }); + const dispatched = await dispatchWorkflow( + workflowID, + ref, + { + aw_context: JSON.stringify(awContext), + }, + defaultBranch + ); if (dispatched) { core.info(`Dispatched '${workflowID}' for '/${commandName}'`); } diff --git a/actions/setup/js/route_slash_command.test.cjs b/actions/setup/js/route_slash_command.test.cjs index 49fc265c4c2..c484a1fd220 100644 --- a/actions/setup/js/route_slash_command.test.cjs +++ b/actions/setup/js/route_slash_command.test.cjs @@ -440,6 +440,62 @@ describe("route_slash_command", () => { expect(globals.core.info).toHaveBeenCalledWith(expect.stringContaining("Completed decentralized label routing for 'smoke'.")); }); + it("falls back to default branch for slash commands when workflow_dispatch trigger is missing on PR branch", async () => { + globals.github.rest.actions.createWorkflowDispatch = vi.fn(async params => { + if (params.ref === "refs/heads/copilot/some-feature-branch") { + throw Object.assign(new Error("Workflow does not have 'workflow_dispatch' trigger"), { + status: 422, + response: { status: 422, data: { message: "Workflow does not have 'workflow_dispatch' trigger" } }, + }); + } + dispatchCalls.push(params); + }); + globals.context.payload = { + issue: { number: 42, pull_request: {} }, + comment: { id: 99, body: "/archie do the thing" }, + }; + globals.github.rest.pulls.get = vi.fn(async () => ({ + data: { number: 42, head: { ref: "copilot/some-feature-branch" } }, + })); + globals.context.payload.repository = { default_branch: "main" }; + + await main(); + + expect(dispatchCalls).toHaveLength(1); + expect(dispatchCalls[0].workflow_id).toBe("archie.lock.yml"); + expect(dispatchCalls[0].ref).toBe("refs/heads/main"); + expect(globals.core.info).toHaveBeenCalledWith(expect.stringContaining("Workflow 'archie.lock.yml' not found on ref 'refs/heads/copilot/some-feature-branch'; retrying on default branch 'refs/heads/main'.")); + }); + + it("falls back to default branch for label routes when workflow_dispatch trigger is missing on PR branch", async () => { + globals.github.rest.actions.createWorkflowDispatch = vi.fn(async params => { + if (params.ref === "refs/heads/copilot/some-feature-branch") { + throw Object.assign(new Error("Workflow does not have 'workflow_dispatch' trigger"), { + status: 422, + response: { status: 422, data: { message: "Workflow does not have 'workflow_dispatch' trigger" } }, + }); + } + dispatchCalls.push(params); + }); + globals.context.eventName = "pull_request"; + globals.context.payload = { + action: "labeled", + label: { name: "ci-doctor" }, + pull_request: { number: 42, head: { ref: "copilot/some-feature-branch" }, state: "open" }, + repository: { default_branch: "main" }, + }; + process.env.GH_AW_LABEL_ROUTING = JSON.stringify({ + "ci-doctor": [{ workflow: "ci-doctor", events: ["pull_request"], ai_reaction: "eyes" }], + }); + + await main(); + + expect(dispatchCalls).toHaveLength(1); + expect(dispatchCalls[0].workflow_id).toBe("ci-doctor.lock.yml"); + expect(dispatchCalls[0].ref).toBe("refs/heads/main"); + expect(globals.core.info).toHaveBeenCalledWith(expect.stringContaining("Workflow 'ci-doctor.lock.yml' not found on ref 'refs/heads/copilot/some-feature-branch'; retrying on default branch 'refs/heads/main'.")); + }); + it("skips centralized routing when PR is closed at workflow start", async () => { globals.context.eventName = "pull_request"; globals.context.payload = { action: "ready_for_review", pull_request: { number: 12, state: "closed" } }; From 90070d1b44ef9dd31b659b156f49ddfb6fb8c5af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Jun 2026 03:18:46 +0000 Subject: [PATCH 2/2] Fix review feedback: narrow trigger check, prevent fallback retry loop Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/route_slash_command.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/route_slash_command.cjs b/actions/setup/js/route_slash_command.cjs index 114ae94d08c..08a3a97818e 100644 --- a/actions/setup/js/route_slash_command.cjs +++ b/actions/setup/js/route_slash_command.cjs @@ -297,7 +297,7 @@ async function dispatchWorkflow(workflowId, ref, inputs, fallbackRef) { } if (isMissingWorkflowDispatchTriggerError(error) && fallbackRef && ref !== fallbackRef) { core.info(`Workflow '${workflowId}' not found on ref '${ref}'; retrying on default branch '${fallbackRef}'.`); - return dispatchWorkflow(workflowId, fallbackRef, inputs); + return dispatchWorkflow(workflowId, fallbackRef, inputs, undefined); } throw new Error(`Failed to dispatch workflow '${workflowId}' on ref '${ref}': ${String(error)}`); } @@ -340,7 +340,7 @@ function isMissingWorkflowDispatchTriggerError(error) { return false; } - return message.includes("does not have 'workflow_dispatch' trigger") || message.includes("no ref found for"); + return message.includes("does not have 'workflow_dispatch' trigger"); } /**