From 9c1b38edcfbe158cb10b6647932fe84ae22a4721 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 06:11:54 +0000 Subject: [PATCH 1/3] Initial plan From be3ae79731016dadda97925cc1229ffd173b8b2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 06:19:13 +0000 Subject: [PATCH 2/3] Fix PR branch ref resolution in dispatch_workflow When running in a pull request context, GITHUB_REF points to the merge ref (refs/pull/{PR_NUMBER}/merge) which is not a valid branch ref for dispatching workflows. This change detects PR contexts using GITHUB_HEAD_REF and constructs the proper branch ref (refs/heads/{branch_name}) for workflow dispatch. Added comprehensive tests for: - PR context branch ref resolution - Non-PR context ref usage - Branch names with slashes - Proper cleanup of PR environment variables in tests Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/dispatch_workflow.cjs | 14 +++- actions/setup/js/dispatch_workflow.test.cjs | 92 +++++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/dispatch_workflow.cjs b/actions/setup/js/dispatch_workflow.cjs index f0bb6aac2ac..69f26773757 100644 --- a/actions/setup/js/dispatch_workflow.cjs +++ b/actions/setup/js/dispatch_workflow.cjs @@ -35,7 +35,19 @@ async function main(config = {}) { // Get the current repository context and ref const repo = context.repo; - const ref = process.env.GITHUB_REF || context.ref || "refs/heads/main"; + + // When running in a PR context, GITHUB_REF points to the merge ref (refs/pull/{PR_NUMBER}/merge) + // which is not a valid branch ref for dispatching workflows. Instead, we need to use + // GITHUB_HEAD_REF which contains the actual PR branch name. + let ref; + if (process.env.GITHUB_HEAD_REF) { + // We're in a pull_request event, use the PR branch ref + ref = `refs/heads/${process.env.GITHUB_HEAD_REF}`; + core.info(`Using PR branch ref: ${ref}`); + } else { + // Use GITHUB_REF for non-PR contexts (push, workflow_dispatch, etc.) + ref = process.env.GITHUB_REF || context.ref || "refs/heads/main"; + } /** * Message handler function that processes a single dispatch_workflow message diff --git a/actions/setup/js/dispatch_workflow.test.cjs b/actions/setup/js/dispatch_workflow.test.cjs index 076ad7e50de..73143ce5e59 100644 --- a/actions/setup/js/dispatch_workflow.test.cjs +++ b/actions/setup/js/dispatch_workflow.test.cjs @@ -29,6 +29,7 @@ describe("dispatch_workflow handler factory", () => { beforeEach(() => { vi.clearAllMocks(); process.env.GITHUB_REF = "refs/heads/main"; + delete process.env.GITHUB_HEAD_REF; // Clean up PR environment variable }); it("should create a handler function", async () => { @@ -262,4 +263,95 @@ describe("dispatch_workflow handler factory", () => { expect(secondDispatchTime - firstDispatchTime).toBeGreaterThanOrEqual(4995); expect(secondDispatchTime - firstDispatchTime).toBeLessThan(6000); }); + + it("should use PR branch ref when GITHUB_HEAD_REF is set", async () => { + // Simulate PR context where GITHUB_REF is the merge ref + process.env.GITHUB_REF = "refs/pull/123/merge"; + process.env.GITHUB_HEAD_REF = "feature-branch"; + + const config = { + workflows: ["test-workflow"], + workflow_files: { + "test-workflow": ".lock.yml", + }, + }; + const handler = await main(config); + + const message = { + type: "dispatch_workflow", + workflow_name: "test-workflow", + inputs: {}, + }; + + await handler(message, {}); + + // Should use the PR branch ref, not the merge ref + expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + workflow_id: "test-workflow.lock.yml", + ref: "refs/heads/feature-branch", + inputs: {}, + }); + }); + + it("should use GITHUB_REF when not in PR context", async () => { + process.env.GITHUB_REF = "refs/heads/main"; + delete process.env.GITHUB_HEAD_REF; + + const config = { + workflows: ["test-workflow"], + workflow_files: { + "test-workflow": ".lock.yml", + }, + }; + const handler = await main(config); + + const message = { + type: "dispatch_workflow", + workflow_name: "test-workflow", + inputs: {}, + }; + + await handler(message, {}); + + // Should use GITHUB_REF directly + expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + workflow_id: "test-workflow.lock.yml", + ref: "refs/heads/main", + inputs: {}, + }); + }); + + it("should handle PR context with slashes in branch names", async () => { + process.env.GITHUB_REF = "refs/pull/456/merge"; + process.env.GITHUB_HEAD_REF = "feature/add-new-feature"; + + const config = { + workflows: ["test-workflow"], + workflow_files: { + "test-workflow": ".lock.yml", + }, + }; + const handler = await main(config); + + const message = { + type: "dispatch_workflow", + workflow_name: "test-workflow", + inputs: {}, + }; + + await handler(message, {}); + + // Should correctly handle branch names with slashes + expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + workflow_id: "test-workflow.lock.yml", + ref: "refs/heads/feature/add-new-feature", + inputs: {}, + }); + }); }); From 7ed0b69b756b448a55a9d0bee0353c5cca0b9ddf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 06:35:42 +0000 Subject: [PATCH 3/3] Resolve default branch correctly when GITHUB_REF is not available Instead of hardcoding "refs/heads/main" as fallback, now properly resolves the repository's default branch by: 1. Checking context.payload.repository.default_branch first 2. Falling back to querying github.rest.repos.get() API 3. Only using "refs/heads/main" as last resort if API call fails Added tests for: - Using repository default branch from context when no GITHUB_REF - Falling back to API when context payload is missing - All existing tests continue to pass Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/dispatch_workflow.cjs | 28 ++++++- actions/setup/js/dispatch_workflow.test.cjs | 87 +++++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/actions/setup/js/dispatch_workflow.cjs b/actions/setup/js/dispatch_workflow.cjs index 69f26773757..625df9469be 100644 --- a/actions/setup/js/dispatch_workflow.cjs +++ b/actions/setup/js/dispatch_workflow.cjs @@ -36,6 +36,26 @@ async function main(config = {}) { // Get the current repository context and ref const repo = context.repo; + // Helper function to get the default branch + const getDefaultBranchRef = async () => { + // Try to get from context payload first + if (context.payload.repository?.default_branch) { + return `refs/heads/${context.payload.repository.default_branch}`; + } + + // Fall back to querying the repository + try { + const { data: repoData } = await github.rest.repos.get({ + owner: repo.owner, + repo: repo.repo, + }); + return `refs/heads/${repoData.default_branch}`; + } catch (error) { + core.warning(`Failed to fetch default branch: ${getErrorMessage(error)}`); + return "refs/heads/main"; + } + }; + // When running in a PR context, GITHUB_REF points to the merge ref (refs/pull/{PR_NUMBER}/merge) // which is not a valid branch ref for dispatching workflows. Instead, we need to use // GITHUB_HEAD_REF which contains the actual PR branch name. @@ -44,9 +64,13 @@ async function main(config = {}) { // We're in a pull_request event, use the PR branch ref ref = `refs/heads/${process.env.GITHUB_HEAD_REF}`; core.info(`Using PR branch ref: ${ref}`); - } else { + } else if (process.env.GITHUB_REF || context.ref) { // Use GITHUB_REF for non-PR contexts (push, workflow_dispatch, etc.) - ref = process.env.GITHUB_REF || context.ref || "refs/heads/main"; + ref = process.env.GITHUB_REF || context.ref; + } else { + // Last resort: fetch the repository's default branch + ref = await getDefaultBranchRef(); + core.info(`Using default branch ref: ${ref}`); } /** diff --git a/actions/setup/js/dispatch_workflow.test.cjs b/actions/setup/js/dispatch_workflow.test.cjs index 73143ce5e59..db5b1a50ae1 100644 --- a/actions/setup/js/dispatch_workflow.test.cjs +++ b/actions/setup/js/dispatch_workflow.test.cjs @@ -15,6 +15,11 @@ global.context = { repo: "test-repo", }, ref: "refs/heads/main", + payload: { + repository: { + default_branch: "main", + }, + }, }; global.github = { @@ -22,6 +27,13 @@ global.github = { actions: { createWorkflowDispatch: vi.fn().mockResolvedValue({}), }, + repos: { + get: vi.fn().mockResolvedValue({ + data: { + default_branch: "main", + }, + }), + }, }, }; @@ -354,4 +366,79 @@ describe("dispatch_workflow handler factory", () => { inputs: {}, }); }); + + it("should use repository default branch when no GITHUB_REF is set", async () => { + delete process.env.GITHUB_REF; + delete process.env.GITHUB_HEAD_REF; + global.context.ref = undefined; + global.context.payload.repository.default_branch = "develop"; + + const config = { + workflows: ["test-workflow"], + workflow_files: { + "test-workflow": ".lock.yml", + }, + }; + const handler = await main(config); + + const message = { + type: "dispatch_workflow", + workflow_name: "test-workflow", + inputs: {}, + }; + + await handler(message, {}); + + // Should use the repository's default branch from context + expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + workflow_id: "test-workflow.lock.yml", + ref: "refs/heads/develop", + inputs: {}, + }); + }); + + it("should fall back to API when context payload is missing", async () => { + delete process.env.GITHUB_REF; + delete process.env.GITHUB_HEAD_REF; + global.context.ref = undefined; + global.context.payload = {}; + + github.rest.repos.get.mockResolvedValueOnce({ + data: { + default_branch: "staging", + }, + }); + + const config = { + workflows: ["test-workflow"], + workflow_files: { + "test-workflow": ".lock.yml", + }, + }; + const handler = await main(config); + + const message = { + type: "dispatch_workflow", + workflow_name: "test-workflow", + inputs: {}, + }; + + await handler(message, {}); + + // Should fetch default branch from API + expect(github.rest.repos.get).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + }); + + expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({ + owner: "test-owner", + repo: "test-repo", + workflow_id: "test-workflow.lock.yml", + ref: "refs/heads/staging", + inputs: {}, + }); + }); });