From c41cca3ef499e83d333545fa2f647246205a6aa2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:52:24 +0000 Subject: [PATCH 1/9] Initial plan From 66546b17d5f3820aff3875ae540b375b3eb24dc3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:14:24 +0000 Subject: [PATCH 2/9] Fix resolve_host_repo.cjs to correctly identify callee repo in cross-org workflow_call When a reusable workflow is invoked via workflow_call from a different org, GITHUB_WORKFLOW_REF incorrectly points to the caller's workflow ref. This caused resolve_host_repo.cjs to resolve to the caller repo instead of the callee (platform) repo. Fix: when workflowRepo === currentRepo (cross-org scenario), fall back to the referenced_workflows API to find the actual callee repo and ref. Also: - Update misleading comment about GITHUB_WORKFLOW_REF behavior in workflow_call - Add resolveFromReferencedWorkflows() helper function - Add 8 new test cases covering cross-org workflow_call scenarios Agent-Logs-Url: https://github.com/github/gh-aw/sessions/443e3746-6d78-453b-adc4-de523452e6da Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.cjs | 102 +++++++++-- actions/setup/js/resolve_host_repo.test.cjs | 187 ++++++++++++++++++++ 2 files changed, 279 insertions(+), 10 deletions(-) diff --git a/actions/setup/js/resolve_host_repo.cjs b/actions/setup/js/resolve_host_repo.cjs index 1811202323a..93b41f42fcb 100644 --- a/actions/setup/js/resolve_host_repo.cjs +++ b/actions/setup/js/resolve_host_repo.cjs @@ -10,13 +10,19 @@ * so the expression introduced in #20301 incorrectly fell back to github.repository * (the caller's repo) instead of the platform repo. * - * GITHUB_WORKFLOW_REF always reflects the currently executing workflow file, not the - * triggering event. Its format is: + * GITHUB_WORKFLOW_REF reflects the currently executing workflow file for most triggers, but + * in cross-org workflow_call scenarios it resolves to the TOP-LEVEL CALLER's workflow ref, + * not the reusable (callee) workflow being executed. Its format is: * owner/repo/.github/workflows/file.yml@refs/heads/main * - * When the platform workflow runs cross-repo (called via uses:), GITHUB_WORKFLOW_REF - * starts with the platform repo slug, while GITHUB_REPOSITORY is the caller repo. - * Comparing the two lets us detect cross-repo invocations without relying on event_name. + * When the platform workflow runs cross-repo (called via uses: from the same org), + * GITHUB_WORKFLOW_REF starts with the platform repo slug, while GITHUB_REPOSITORY is the + * caller repo. Comparing the two lets us detect cross-repo invocations without relying on + * event_name. + * + * For cross-org workflow_call, GITHUB_WORKFLOW_REF and GITHUB_REPOSITORY both resolve to + * the caller's repo. In that case we fall back to the referenced_workflows API lookup to + * find the actual callee (platform) repo and ref. * * In a caller-hosted relay pinned to a feature branch (e.g. uses: platform/.github/workflows/ * gateway.lock.yml@feature-branch), the @feature-branch portion is encoded in @@ -24,13 +30,71 @@ * the correct branch rather than the platform repo's default branch. * * SEC-005: The targetRepo and targetRef values are resolved solely from trusted system - * environment variables (GITHUB_WORKFLOW_REF, GITHUB_REPOSITORY, GITHUB_REF) set by the - * GitHub Actions runtime. They are not derived from user-supplied input, so no allowlist - * check is required in this handler. + * environment variables (GITHUB_WORKFLOW_REF, GITHUB_REPOSITORY, GITHUB_REF) and the + * GitHub Actions API (referenced_workflows), set/provided by the GitHub Actions runtime. + * They are not derived from user-supplied input, so no allowlist check is required here. * * @safe-outputs-exempt SEC-005: values sourced from trusted runtime env vars only */ +/** + * Attempts to resolve the callee repository and ref from the referenced_workflows API. + * + * This is used as a fallback when GITHUB_WORKFLOW_REF points to the same repo as + * GITHUB_REPOSITORY (cross-org workflow_call scenario), because in that case + * GITHUB_WORKFLOW_REF reflects the caller's workflow ref, not the callee's. + * + * @param {string} currentRepo - The value of GITHUB_REPOSITORY (owner/repo) + * @returns {Promise<{repo: string, ref: string} | null>} Resolved callee repo and ref, or null + */ +async function resolveFromReferencedWorkflows(currentRepo) { + const runId = parseInt(process.env.GITHUB_RUN_ID || String(context.runId), 10); + if (!Number.isFinite(runId)) { + core.info("Run ID is unavailable or invalid, cannot perform referenced_workflows lookup"); + return null; + } + + const [runOwner, runRepo] = currentRepo.split("/"); + try { + core.info(`Checking for cross-org callee via referenced_workflows API (run ${runId})`); + const runResponse = await github.rest.actions.getWorkflowRun({ + owner: runOwner, + repo: runRepo, + run_id: runId, + }); + + const referencedWorkflows = runResponse.data.referenced_workflows || []; + core.info(`Found ${referencedWorkflows.length} referenced workflow(s) in run`); + + // Find the first referenced workflow from a different repo than the caller. + // In cross-org workflow_call, the callee (platform) repo is different from currentRepo + // (the caller's repo). For same-repo invocations there will be no cross-repo entry. + const matchingEntry = referencedWorkflows.find(wf => { + const pathRepoMatch = wf.path.match(/^([^/]+\/[^/]+)\//); + const entryRepo = pathRepoMatch ? pathRepoMatch[1] : ""; + return entryRepo && entryRepo !== currentRepo; + }); + + if (matchingEntry) { + const pathRepoMatch = matchingEntry.path.match(/^([^/]+\/[^/]+)\//); + const calleeRepo = pathRepoMatch ? pathRepoMatch[1] : ""; + // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref. + const pathRefMatch = matchingEntry.path.match(/@(.+)$/); + const calleeRef = matchingEntry.sha || matchingEntry.ref || (pathRefMatch ? pathRefMatch[1] : ""); + core.info(`Resolved callee repo from referenced_workflows: ${calleeRepo} @ ${calleeRef || "(default branch)"}`); + core.info(` Referenced workflow path: ${matchingEntry.path}`); + return { repo: calleeRepo, ref: calleeRef }; + } else { + core.info("No cross-org callee found in referenced_workflows, using current repo"); + return null; + } + } catch (error) { + const msg = error instanceof Error ? error.message : String(error); + core.info(`Could not fetch referenced_workflows from API: ${msg}, using current repo`); + return null; + } +} + /** * @returns {Promise} */ @@ -44,7 +108,7 @@ async function main() { const workflowRepo = repoMatch ? repoMatch[1] : ""; // Fall back to currentRepo when GITHUB_WORKFLOW_REF cannot be parsed - const targetRepo = workflowRepo || currentRepo; + let targetRepo = workflowRepo || currentRepo; // Extract the ref portion after '@' from GITHUB_WORKFLOW_REF. // GITHUB_WORKFLOW_REF format: owner/repo/.github/workflows/file.yml@ref @@ -57,7 +121,25 @@ async function main() { // scenarios GITHUB_REF is the *caller* repo's ref, not the callee's, and using it // would check out the wrong branch. const refMatch = workflowRef.match(/@(.+)$/); - const targetRef = refMatch ? refMatch[1] : ""; + let targetRef = refMatch ? refMatch[1] : ""; + + // Cross-org workflow_call detection: when GITHUB_WORKFLOW_REF points to the same repo as + // GITHUB_REPOSITORY, it means GITHUB_WORKFLOW_REF is resolving to the caller's workflow + // (not the callee's). This happens in cross-org workflow_call invocations where GitHub + // Actions sets GITHUB_WORKFLOW_REF to the top-level caller's workflow ref rather than the + // reusable workflow being executed. In that case, fall back to the referenced_workflows API + // to find the actual callee (platform) repo and ref. + // + // Note: GITHUB_EVENT_NAME inside a reusable workflow reflects the ORIGINAL trigger event + // (e.g., "push", "issues"), NOT "workflow_call", so we cannot use event_name to detect + // this scenario. + if (workflowRepo && workflowRepo === currentRepo) { + const resolved = await resolveFromReferencedWorkflows(currentRepo); + if (resolved) { + targetRepo = resolved.repo; + targetRef = resolved.ref || targetRef; + } + } core.info(`GITHUB_WORKFLOW_REF: ${workflowRef}`); core.info(`GITHUB_REPOSITORY: ${currentRepo}`); diff --git a/actions/setup/js/resolve_host_repo.test.cjs b/actions/setup/js/resolve_host_repo.test.cjs index a4a0afb1b98..40c77ce4448 100644 --- a/actions/setup/js/resolve_host_repo.test.cjs +++ b/actions/setup/js/resolve_host_repo.test.cjs @@ -14,8 +14,31 @@ const mockCore = { }, }; +const mockGetWorkflowRun = vi.fn(); +const mockGithub = { + rest: { + actions: { + getWorkflowRun: mockGetWorkflowRun, + }, + }, +}; + +const mockContext = { + runId: 99999, +}; + // Set up global mocks before importing the module global.core = mockCore; +global.github = mockGithub; +global.context = mockContext; + +/** + * Creates a default mock response for getWorkflowRun with no referenced workflows. + * Used for same-repo and same-org cross-repo tests where the API should not change the result. + */ +function mockNoReferencedWorkflows() { + mockGetWorkflowRun.mockResolvedValue({ data: { referenced_workflows: [] } }); +} describe("resolve_host_repo.cjs", () => { let main; @@ -24,6 +47,8 @@ describe("resolve_host_repo.cjs", () => { vi.clearAllMocks(); mockCore.summary.addRaw.mockReturnThis(); mockCore.summary.write.mockResolvedValue(undefined); + // Default: no referenced workflows (same-repo or same-org cross-repo invocations) + mockNoReferencedWorkflows(); const module = await import("./resolve_host_repo.cjs"); main = module.main; @@ -33,6 +58,7 @@ describe("resolve_host_repo.cjs", () => { delete process.env.GITHUB_WORKFLOW_REF; delete process.env.GITHUB_REPOSITORY; delete process.env.GITHUB_REF; + delete process.env.GITHUB_RUN_ID; }); it("should output the platform repo when invoked cross-repo", async () => { @@ -235,4 +261,165 @@ describe("resolve_host_repo.cjs", () => { expect(mockCore.summary.addRaw).toHaveBeenCalledWith(expect.stringContaining("refs/heads/feature-branch")); expect(mockCore.summary.write).toHaveBeenCalled(); }); + + describe("cross-org workflow_call scenarios", () => { + it("should resolve callee repo via referenced_workflows API when GITHUB_WORKFLOW_REF matches GITHUB_REPOSITORY", async () => { + // Cross-org workflow_call: GITHUB_WORKFLOW_REF points to the caller's repo (not the callee), + // so workflowRepo === currentRepo. The referenced_workflows API returns the actual callee. + process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + mockGetWorkflowRun.mockResolvedValue({ + data: { + referenced_workflows: [ + { + path: "platform-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main", + sha: "abc123def456", + ref: "refs/heads/main", + }, + ], + }, + }); + + await main(); + + expect(mockGetWorkflowRun).toHaveBeenCalledWith({ + owner: "caller-org", + repo: "caller-repo", + run_id: 12345, + }); + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "platform-org/platform-repo"); + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo_name", "platform-repo"); + // sha is preferred over ref + expect(mockCore.setOutput).toHaveBeenCalledWith("target_ref", "abc123def456"); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Resolved callee repo from referenced_workflows")); + }); + + it("should use ref from referenced_workflows entry when sha is absent", async () => { + process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + mockGetWorkflowRun.mockResolvedValue({ + data: { + referenced_workflows: [ + { + path: "platform-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/feature", + sha: undefined, + ref: "refs/heads/feature", + }, + ], + }, + }); + + await main(); + + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "platform-org/platform-repo"); + expect(mockCore.setOutput).toHaveBeenCalledWith("target_ref", "refs/heads/feature"); + }); + + it("should fall back to path-parsed ref when sha and ref are absent in referenced_workflows", async () => { + process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + mockGetWorkflowRun.mockResolvedValue({ + data: { + referenced_workflows: [ + { + path: "platform-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/stable", + sha: undefined, + ref: undefined, + }, + ], + }, + }); + + await main(); + + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "platform-org/platform-repo"); + expect(mockCore.setOutput).toHaveBeenCalledWith("target_ref", "refs/heads/stable"); + }); + + it("should log cross-repo detection and write step summary for cross-org callee", async () => { + process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + mockGetWorkflowRun.mockResolvedValue({ + data: { + referenced_workflows: [ + { + path: "platform-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main", + sha: "abc123", + ref: "refs/heads/main", + }, + ], + }, + }); + + await main(); + + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Cross-repo invocation detected")); + expect(mockCore.summary.addRaw).toHaveBeenCalledWith(expect.stringContaining("platform-org/platform-repo")); + expect(mockCore.summary.write).toHaveBeenCalled(); + }); + + it("should fall back to GITHUB_REPOSITORY when referenced_workflows has no cross-org entry", async () => { + // workflowRepo === currentRepo but no cross-org entry (same-org same-repo, no callee) + process.env.GITHUB_WORKFLOW_REF = "my-org/my-repo/.github/workflows/my-workflow.lock.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "my-org/my-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + mockGetWorkflowRun.mockResolvedValue({ data: { referenced_workflows: [] } }); + + await main(); + + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "my-org/my-repo"); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No cross-org callee found in referenced_workflows")); + }); + + it("should fall back gracefully when referenced_workflows API call fails", async () => { + process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + mockGetWorkflowRun.mockRejectedValue(new Error("API unavailable")); + + await main(); + + // Should fall back to the currentRepo (caller) — not ideal but safe degradation + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "caller-org/caller-repo"); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("API unavailable")); + }); + + it("should fall back gracefully when GITHUB_RUN_ID is missing", async () => { + process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; + delete process.env.GITHUB_RUN_ID; + mockContext.runId = NaN; + + await main(); + + expect(mockGetWorkflowRun).not.toHaveBeenCalled(); + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "caller-org/caller-repo"); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Run ID is unavailable or invalid")); + + // Restore runId for other tests + mockContext.runId = 99999; + }); + + it("should not call referenced_workflows API for normal cross-repo (same-org) invocations", async () => { + // workflowRepo !== currentRepo → no API call needed + process.env.GITHUB_WORKFLOW_REF = "my-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "my-org/app-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + await main(); + + expect(mockGetWorkflowRun).not.toHaveBeenCalled(); + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "my-org/platform-repo"); + }); + }); }); From 4ff79f72721ad0a36044df867b2bdc9b40ae1ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:16:29 +0000 Subject: [PATCH 3/9] Address code review feedback: safer runId extraction and test state cleanup - Replace String(context.runId) with an explicit type check to avoid parseInt('undefined', 10) edge case - Move mockContext.runId reset to afterEach instead of inline in test Agent-Logs-Url: https://github.com/github/gh-aw/sessions/443e3746-6d78-453b-adc4-de523452e6da Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.cjs | 3 ++- actions/setup/js/resolve_host_repo.test.cjs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/resolve_host_repo.cjs b/actions/setup/js/resolve_host_repo.cjs index 93b41f42fcb..d114e61a50c 100644 --- a/actions/setup/js/resolve_host_repo.cjs +++ b/actions/setup/js/resolve_host_repo.cjs @@ -48,7 +48,8 @@ * @returns {Promise<{repo: string, ref: string} | null>} Resolved callee repo and ref, or null */ async function resolveFromReferencedWorkflows(currentRepo) { - const runId = parseInt(process.env.GITHUB_RUN_ID || String(context.runId), 10); + const rawRunId = process.env.GITHUB_RUN_ID; + const runId = rawRunId ? parseInt(rawRunId, 10) : typeof context.runId === "number" && Number.isFinite(context.runId) ? context.runId : NaN; if (!Number.isFinite(runId)) { core.info("Run ID is unavailable or invalid, cannot perform referenced_workflows lookup"); return null; diff --git a/actions/setup/js/resolve_host_repo.test.cjs b/actions/setup/js/resolve_host_repo.test.cjs index 40c77ce4448..ce9b0871987 100644 --- a/actions/setup/js/resolve_host_repo.test.cjs +++ b/actions/setup/js/resolve_host_repo.test.cjs @@ -59,6 +59,8 @@ describe("resolve_host_repo.cjs", () => { delete process.env.GITHUB_REPOSITORY; delete process.env.GITHUB_REF; delete process.env.GITHUB_RUN_ID; + // Reset context.runId to the default value to prevent test state leakage + mockContext.runId = 99999; }); it("should output the platform repo when invoked cross-repo", async () => { @@ -405,9 +407,6 @@ describe("resolve_host_repo.cjs", () => { expect(mockGetWorkflowRun).not.toHaveBeenCalled(); expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "caller-org/caller-repo"); expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Run ID is unavailable or invalid")); - - // Restore runId for other tests - mockContext.runId = 99999; }); it("should not call referenced_workflows API for normal cross-repo (same-org) invocations", async () => { From 51fb1ca18cdad48c31e7378c1ded0404da68a07f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:18:22 +0000 Subject: [PATCH 4/9] Refactor: extract REPO_PREFIX_RE constant and improve runId readability - Extract /^([^/]+\/[^/]+)\// regex into named REPO_PREFIX_RE constant to eliminate duplication and improve maintainability - Replace nested ternary for runId extraction with clear if-else blocks Agent-Logs-Url: https://github.com/github/gh-aw/sessions/443e3746-6d78-453b-adc4-de523452e6da Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.cjs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/actions/setup/js/resolve_host_repo.cjs b/actions/setup/js/resolve_host_repo.cjs index d114e61a50c..d280fb855b8 100644 --- a/actions/setup/js/resolve_host_repo.cjs +++ b/actions/setup/js/resolve_host_repo.cjs @@ -37,6 +37,9 @@ * @safe-outputs-exempt SEC-005: values sourced from trusted runtime env vars only */ +// Matches the "owner/repo" prefix from a GitHub workflow path of the form "owner/repo/...". +const REPO_PREFIX_RE = /^([^/]+\/[^/]+)\//; + /** * Attempts to resolve the callee repository and ref from the referenced_workflows API. * @@ -49,7 +52,14 @@ */ async function resolveFromReferencedWorkflows(currentRepo) { const rawRunId = process.env.GITHUB_RUN_ID; - const runId = rawRunId ? parseInt(rawRunId, 10) : typeof context.runId === "number" && Number.isFinite(context.runId) ? context.runId : NaN; + let runId; + if (rawRunId) { + runId = parseInt(rawRunId, 10); + } else if (typeof context.runId === "number" && Number.isFinite(context.runId)) { + runId = context.runId; + } else { + runId = NaN; + } if (!Number.isFinite(runId)) { core.info("Run ID is unavailable or invalid, cannot perform referenced_workflows lookup"); return null; @@ -71,13 +81,13 @@ async function resolveFromReferencedWorkflows(currentRepo) { // In cross-org workflow_call, the callee (platform) repo is different from currentRepo // (the caller's repo). For same-repo invocations there will be no cross-repo entry. const matchingEntry = referencedWorkflows.find(wf => { - const pathRepoMatch = wf.path.match(/^([^/]+\/[^/]+)\//); + const pathRepoMatch = wf.path.match(REPO_PREFIX_RE); const entryRepo = pathRepoMatch ? pathRepoMatch[1] : ""; return entryRepo && entryRepo !== currentRepo; }); if (matchingEntry) { - const pathRepoMatch = matchingEntry.path.match(/^([^/]+\/[^/]+)\//); + const pathRepoMatch = matchingEntry.path.match(REPO_PREFIX_RE); const calleeRepo = pathRepoMatch ? pathRepoMatch[1] : ""; // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref. const pathRefMatch = matchingEntry.path.match(/@(.+)$/); @@ -105,7 +115,7 @@ async function main() { // GITHUB_WORKFLOW_REF format: owner/repo/.github/workflows/file.yml@ref // The regex captures everything before the third slash segment (i.e., the owner/repo prefix). - const repoMatch = workflowRef.match(/^([^/]+\/[^/]+)\//); + const repoMatch = workflowRef.match(REPO_PREFIX_RE); const workflowRepo = repoMatch ? repoMatch[1] : ""; // Fall back to currentRepo when GITHUB_WORKFLOW_REF cannot be parsed From 5560c5ab13b483b72fb7d6117488597151111746 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:21:21 +0000 Subject: [PATCH 5/9] Address final code review feedback: simplify runId, deduplicate regex, explicit test mocks - Simplify runId extraction to use ternary (removing NaN-producing String cast) - Use for-loop to capture repo from REPO_PREFIX_RE match once (not twice) - Remove default mockNoReferencedWorkflows() from beforeEach; add it explicitly to same-repo tests that trigger the referenced_workflows API path Agent-Logs-Url: https://github.com/github/gh-aw/sessions/443e3746-6d78-453b-adc4-de523452e6da Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.cjs | 24 ++++++++++----------- actions/setup/js/resolve_host_repo.test.cjs | 5 +++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/actions/setup/js/resolve_host_repo.cjs b/actions/setup/js/resolve_host_repo.cjs index d280fb855b8..4552f03da64 100644 --- a/actions/setup/js/resolve_host_repo.cjs +++ b/actions/setup/js/resolve_host_repo.cjs @@ -52,14 +52,7 @@ const REPO_PREFIX_RE = /^([^/]+\/[^/]+)\//; */ async function resolveFromReferencedWorkflows(currentRepo) { const rawRunId = process.env.GITHUB_RUN_ID; - let runId; - if (rawRunId) { - runId = parseInt(rawRunId, 10); - } else if (typeof context.runId === "number" && Number.isFinite(context.runId)) { - runId = context.runId; - } else { - runId = NaN; - } + const runId = rawRunId ? parseInt(rawRunId, 10) : typeof context.runId === "number" ? context.runId : NaN; if (!Number.isFinite(runId)) { core.info("Run ID is unavailable or invalid, cannot perform referenced_workflows lookup"); return null; @@ -80,15 +73,20 @@ async function resolveFromReferencedWorkflows(currentRepo) { // Find the first referenced workflow from a different repo than the caller. // In cross-org workflow_call, the callee (platform) repo is different from currentRepo // (the caller's repo). For same-repo invocations there will be no cross-repo entry. - const matchingEntry = referencedWorkflows.find(wf => { + // Capture the repo from the path match so we don't run the regex twice. + let calleeRepo = ""; + let matchingEntry = null; + for (const wf of referencedWorkflows) { const pathRepoMatch = wf.path.match(REPO_PREFIX_RE); const entryRepo = pathRepoMatch ? pathRepoMatch[1] : ""; - return entryRepo && entryRepo !== currentRepo; - }); + if (entryRepo && entryRepo !== currentRepo) { + matchingEntry = wf; + calleeRepo = entryRepo; + break; + } + } if (matchingEntry) { - const pathRepoMatch = matchingEntry.path.match(REPO_PREFIX_RE); - const calleeRepo = pathRepoMatch ? pathRepoMatch[1] : ""; // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref. const pathRefMatch = matchingEntry.path.match(/@(.+)$/); const calleeRef = matchingEntry.sha || matchingEntry.ref || (pathRefMatch ? pathRefMatch[1] : ""); diff --git a/actions/setup/js/resolve_host_repo.test.cjs b/actions/setup/js/resolve_host_repo.test.cjs index ce9b0871987..e1c35b1362d 100644 --- a/actions/setup/js/resolve_host_repo.test.cjs +++ b/actions/setup/js/resolve_host_repo.test.cjs @@ -47,8 +47,6 @@ describe("resolve_host_repo.cjs", () => { vi.clearAllMocks(); mockCore.summary.addRaw.mockReturnThis(); mockCore.summary.write.mockResolvedValue(undefined); - // Default: no referenced workflows (same-repo or same-org cross-repo invocations) - mockNoReferencedWorkflows(); const module = await import("./resolve_host_repo.cjs"); main = module.main; @@ -86,6 +84,7 @@ describe("resolve_host_repo.cjs", () => { it("should output the current repo when same-repo invocation", async () => { process.env.GITHUB_WORKFLOW_REF = "my-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main"; process.env.GITHUB_REPOSITORY = "my-org/platform-repo"; + mockNoReferencedWorkflows(); await main(); @@ -96,6 +95,7 @@ describe("resolve_host_repo.cjs", () => { it("should not write step summary for same-repo invocations", async () => { process.env.GITHUB_WORKFLOW_REF = "my-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main"; process.env.GITHUB_REPOSITORY = "my-org/platform-repo"; + mockNoReferencedWorkflows(); await main(); @@ -239,6 +239,7 @@ describe("resolve_host_repo.cjs", () => { it("should output target_repo_name when same-repo invocation", async () => { process.env.GITHUB_WORKFLOW_REF = "my-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main"; process.env.GITHUB_REPOSITORY = "my-org/platform-repo"; + mockNoReferencedWorkflows(); await main(); From c61dc1edf4c39249fb31f6aa1a6979b07057b24b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:43:57 +0000 Subject: [PATCH 6/9] Address PR review comments: ambiguity guard, safe-outputs-exempt tag, mock isolation - Fix @safe-outputs-exempt tag to mention referenced_workflows API as a source - Add ambiguity guard: bail out when multiple cross-org candidates are found instead of silently picking the first one; log all candidates for diagnostics - Remove dead else branch (unreachable after early returns) - Use mockResolvedValueOnce/mockRejectedValueOnce in all tests for explicit per-test mock behavior; add mockGetWorkflowRun.mockReset() in beforeEach to prevent implementation leakage (vi.clearAllMocks leaves implementations) - Add test case for the new ambiguous-candidates scenario Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b0fccfc0-dca3-45d0-9563-990857fb27ec Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.cjs | 43 ++++++++++-------- actions/setup/js/resolve_host_repo.test.cjs | 50 +++++++++++++++++---- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/actions/setup/js/resolve_host_repo.cjs b/actions/setup/js/resolve_host_repo.cjs index 4552f03da64..58fd124bfc5 100644 --- a/actions/setup/js/resolve_host_repo.cjs +++ b/actions/setup/js/resolve_host_repo.cjs @@ -34,7 +34,7 @@ * GitHub Actions API (referenced_workflows), set/provided by the GitHub Actions runtime. * They are not derived from user-supplied input, so no allowlist check is required here. * - * @safe-outputs-exempt SEC-005: values sourced from trusted runtime env vars only + * @safe-outputs-exempt SEC-005: values sourced from trusted GitHub Actions runtime env vars and referenced_workflows API only */ // Matches the "owner/repo" prefix from a GitHub workflow path of the form "owner/repo/...". @@ -70,33 +70,40 @@ async function resolveFromReferencedWorkflows(currentRepo) { const referencedWorkflows = runResponse.data.referenced_workflows || []; core.info(`Found ${referencedWorkflows.length} referenced workflow(s) in run`); - // Find the first referenced workflow from a different repo than the caller. - // In cross-org workflow_call, the callee (platform) repo is different from currentRepo - // (the caller's repo). For same-repo invocations there will be no cross-repo entry. - // Capture the repo from the path match so we don't run the regex twice. - let calleeRepo = ""; - let matchingEntry = null; + // Collect all referenced workflows from a different repo than the caller. + // In cross-org workflow_call, the callee (platform) repo is different from currentRepo. + // If multiple cross-repo candidates are found we cannot safely pick one, so we bail out. + const crossRepoCandidates = []; for (const wf of referencedWorkflows) { const pathRepoMatch = wf.path.match(REPO_PREFIX_RE); const entryRepo = pathRepoMatch ? pathRepoMatch[1] : ""; if (entryRepo && entryRepo !== currentRepo) { - matchingEntry = wf; - calleeRepo = entryRepo; - break; + crossRepoCandidates.push({ wf, repo: entryRepo }); } } - if (matchingEntry) { - // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref. - const pathRefMatch = matchingEntry.path.match(/@(.+)$/); - const calleeRef = matchingEntry.sha || matchingEntry.ref || (pathRefMatch ? pathRefMatch[1] : ""); - core.info(`Resolved callee repo from referenced_workflows: ${calleeRepo} @ ${calleeRef || "(default branch)"}`); - core.info(` Referenced workflow path: ${matchingEntry.path}`); - return { repo: calleeRepo, ref: calleeRef }; - } else { + if (crossRepoCandidates.length === 0) { core.info("No cross-org callee found in referenced_workflows, using current repo"); return null; } + + if (crossRepoCandidates.length > 1) { + core.info(`Referenced workflows lookup is ambiguous; found ${crossRepoCandidates.length} cross-repo candidates, not selecting one`); + for (const candidate of crossRepoCandidates) { + core.info(` Candidate referenced workflow path: ${candidate.wf.path}`); + } + return null; + } + + const matchingEntry = crossRepoCandidates[0].wf; + const calleeRepo = crossRepoCandidates[0].repo; + + // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref. + const pathRefMatch = matchingEntry.path.match(/@(.+)$/); + const calleeRef = matchingEntry.sha || matchingEntry.ref || (pathRefMatch ? pathRefMatch[1] : ""); + core.info(`Resolved callee repo from referenced_workflows: ${calleeRepo} @ ${calleeRef || "(default branch)"}`); + core.info(` Referenced workflow path: ${matchingEntry.path}`); + return { repo: calleeRepo, ref: calleeRef }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); core.info(`Could not fetch referenced_workflows from API: ${msg}, using current repo`); diff --git a/actions/setup/js/resolve_host_repo.test.cjs b/actions/setup/js/resolve_host_repo.test.cjs index e1c35b1362d..a3ebcf1173f 100644 --- a/actions/setup/js/resolve_host_repo.test.cjs +++ b/actions/setup/js/resolve_host_repo.test.cjs @@ -33,11 +33,11 @@ global.github = mockGithub; global.context = mockContext; /** - * Creates a default mock response for getWorkflowRun with no referenced workflows. + * Sets up a one-time mock response for getWorkflowRun with no referenced workflows. * Used for same-repo and same-org cross-repo tests where the API should not change the result. */ function mockNoReferencedWorkflows() { - mockGetWorkflowRun.mockResolvedValue({ data: { referenced_workflows: [] } }); + mockGetWorkflowRun.mockResolvedValueOnce({ data: { referenced_workflows: [] } }); } describe("resolve_host_repo.cjs", () => { @@ -45,6 +45,10 @@ describe("resolve_host_repo.cjs", () => { beforeEach(async () => { vi.clearAllMocks(); + // Reset mock implementation to prevent leakage between tests. + // vi.clearAllMocks() clears call history but does not reset implementations + // set via mockResolvedValue(). mockReset() clears both. + mockGetWorkflowRun.mockReset(); mockCore.summary.addRaw.mockReturnThis(); mockCore.summary.write.mockResolvedValue(undefined); @@ -273,7 +277,7 @@ describe("resolve_host_repo.cjs", () => { process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; process.env.GITHUB_RUN_ID = "12345"; - mockGetWorkflowRun.mockResolvedValue({ + mockGetWorkflowRun.mockResolvedValueOnce({ data: { referenced_workflows: [ { @@ -304,7 +308,7 @@ describe("resolve_host_repo.cjs", () => { process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; process.env.GITHUB_RUN_ID = "12345"; - mockGetWorkflowRun.mockResolvedValue({ + mockGetWorkflowRun.mockResolvedValueOnce({ data: { referenced_workflows: [ { @@ -327,7 +331,7 @@ describe("resolve_host_repo.cjs", () => { process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; process.env.GITHUB_RUN_ID = "12345"; - mockGetWorkflowRun.mockResolvedValue({ + mockGetWorkflowRun.mockResolvedValueOnce({ data: { referenced_workflows: [ { @@ -350,7 +354,7 @@ describe("resolve_host_repo.cjs", () => { process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; process.env.GITHUB_RUN_ID = "12345"; - mockGetWorkflowRun.mockResolvedValue({ + mockGetWorkflowRun.mockResolvedValueOnce({ data: { referenced_workflows: [ { @@ -375,7 +379,7 @@ describe("resolve_host_repo.cjs", () => { process.env.GITHUB_REPOSITORY = "my-org/my-repo"; process.env.GITHUB_RUN_ID = "12345"; - mockGetWorkflowRun.mockResolvedValue({ data: { referenced_workflows: [] } }); + mockGetWorkflowRun.mockResolvedValueOnce({ data: { referenced_workflows: [] } }); await main(); @@ -383,12 +387,42 @@ describe("resolve_host_repo.cjs", () => { expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("No cross-org callee found in referenced_workflows")); }); + it("should fall back to GITHUB_REPOSITORY when referenced_workflows has multiple cross-org entries (ambiguous)", async () => { + // Cannot safely select one callee when multiple cross-repo workflows are referenced. + process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; + process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; + process.env.GITHUB_RUN_ID = "12345"; + + mockGetWorkflowRun.mockResolvedValueOnce({ + data: { + referenced_workflows: [ + { + path: "platform-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main", + sha: "abc123", + ref: "refs/heads/main", + }, + { + path: "other-org/other-repo/.github/workflows/other.lock.yml@refs/heads/main", + sha: "def456", + ref: "refs/heads/main", + }, + ], + }, + }); + + await main(); + + // Falls back to currentRepo since the result is ambiguous + expect(mockCore.setOutput).toHaveBeenCalledWith("target_repo", "caller-org/caller-repo"); + expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Referenced workflows lookup is ambiguous")); + }); + it("should fall back gracefully when referenced_workflows API call fails", async () => { process.env.GITHUB_WORKFLOW_REF = "caller-org/caller-repo/.github/workflows/relay.yml@refs/heads/main"; process.env.GITHUB_REPOSITORY = "caller-org/caller-repo"; process.env.GITHUB_RUN_ID = "12345"; - mockGetWorkflowRun.mockRejectedValue(new Error("API unavailable")); + mockGetWorkflowRun.mockRejectedValueOnce(new Error("API unavailable")); await main(); From 89801cc409e1035feca876561e9aea7aa96e4134 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 00:45:32 +0000 Subject: [PATCH 7/9] Polish: rename mockNoReferencedWorkflowsOnce and update beforeEach comment Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b0fccfc0-dca3-45d0-9563-990857fb27ec Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.test.cjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/actions/setup/js/resolve_host_repo.test.cjs b/actions/setup/js/resolve_host_repo.test.cjs index a3ebcf1173f..cdf3937d31a 100644 --- a/actions/setup/js/resolve_host_repo.test.cjs +++ b/actions/setup/js/resolve_host_repo.test.cjs @@ -36,7 +36,7 @@ global.context = mockContext; * Sets up a one-time mock response for getWorkflowRun with no referenced workflows. * Used for same-repo and same-org cross-repo tests where the API should not change the result. */ -function mockNoReferencedWorkflows() { +function mockNoReferencedWorkflowsOnce() { mockGetWorkflowRun.mockResolvedValueOnce({ data: { referenced_workflows: [] } }); } @@ -45,9 +45,9 @@ describe("resolve_host_repo.cjs", () => { beforeEach(async () => { vi.clearAllMocks(); - // Reset mock implementation to prevent leakage between tests. - // vi.clearAllMocks() clears call history but does not reset implementations - // set via mockResolvedValue(). mockReset() clears both. + // Defensive reset of mock implementation as a safety measure. + // All tests use *Once variants, but mockReset() ensures no state leaks + // if a test adds a persistent mock or if a future test omits the Once variant. mockGetWorkflowRun.mockReset(); mockCore.summary.addRaw.mockReturnThis(); mockCore.summary.write.mockResolvedValue(undefined); @@ -88,7 +88,7 @@ describe("resolve_host_repo.cjs", () => { it("should output the current repo when same-repo invocation", async () => { process.env.GITHUB_WORKFLOW_REF = "my-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main"; process.env.GITHUB_REPOSITORY = "my-org/platform-repo"; - mockNoReferencedWorkflows(); + mockNoReferencedWorkflowsOnce(); await main(); @@ -99,7 +99,7 @@ describe("resolve_host_repo.cjs", () => { it("should not write step summary for same-repo invocations", async () => { process.env.GITHUB_WORKFLOW_REF = "my-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main"; process.env.GITHUB_REPOSITORY = "my-org/platform-repo"; - mockNoReferencedWorkflows(); + mockNoReferencedWorkflowsOnce(); await main(); @@ -243,7 +243,7 @@ describe("resolve_host_repo.cjs", () => { it("should output target_repo_name when same-repo invocation", async () => { process.env.GITHUB_WORKFLOW_REF = "my-org/platform-repo/.github/workflows/gateway.lock.yml@refs/heads/main"; process.env.GITHUB_REPOSITORY = "my-org/platform-repo"; - mockNoReferencedWorkflows(); + mockNoReferencedWorkflowsOnce(); await main(); From 49e9b1d4cfeff40e50165d0526f921163cfc54af Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 01:11:20 +0000 Subject: [PATCH 8/9] Improve logging in resolve_host_repo.cjs for easier debugging - resolveFromReferencedWorkflows: log currentRepo and runId at entry; dump each referenced_workflow entry (path/sha/ref) as they're scanned; log cross-repo candidate count; log which ref source was used (sha/ref/path/none) - main: log all three env vars (GITHUB_WORKFLOW_REF, GITHUB_REPOSITORY, GITHUB_RUN_ID) at the start; log parsed workflowRepo and targetRef; log which code path was taken (cross-org API lookup, parse failure, same-org cross-repo, fallback no-result); log final resolved values and outputs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/aed19af3-103b-45e4-96bc-9c16ed33477a Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.cjs | 29 ++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/actions/setup/js/resolve_host_repo.cjs b/actions/setup/js/resolve_host_repo.cjs index 58fd124bfc5..7b1c1e8cd09 100644 --- a/actions/setup/js/resolve_host_repo.cjs +++ b/actions/setup/js/resolve_host_repo.cjs @@ -60,7 +60,7 @@ async function resolveFromReferencedWorkflows(currentRepo) { const [runOwner, runRepo] = currentRepo.split("/"); try { - core.info(`Checking for cross-org callee via referenced_workflows API (run ${runId})`); + core.info(`Checking for cross-org callee via referenced_workflows API (run ${runId}, repo ${currentRepo})`); const runResponse = await github.rest.actions.getWorkflowRun({ owner: runOwner, repo: runRepo, @@ -69,6 +69,9 @@ async function resolveFromReferencedWorkflows(currentRepo) { const referencedWorkflows = runResponse.data.referenced_workflows || []; core.info(`Found ${referencedWorkflows.length} referenced workflow(s) in run`); + for (const wf of referencedWorkflows) { + core.info(` referenced workflow: path=${wf.path} sha=${wf.sha || "(none)"} ref=${wf.ref || "(none)"}`); + } // Collect all referenced workflows from a different repo than the caller. // In cross-org workflow_call, the callee (platform) repo is different from currentRepo. @@ -81,6 +84,7 @@ async function resolveFromReferencedWorkflows(currentRepo) { crossRepoCandidates.push({ wf, repo: entryRepo }); } } + core.info(`Found ${crossRepoCandidates.length} cross-repo candidate(s) (excluding current repo ${currentRepo})`); if (crossRepoCandidates.length === 0) { core.info("No cross-org callee found in referenced_workflows, using current repo"); @@ -100,8 +104,9 @@ async function resolveFromReferencedWorkflows(currentRepo) { // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref. const pathRefMatch = matchingEntry.path.match(/@(.+)$/); + const calleeRefSource = matchingEntry.sha ? "sha" : matchingEntry.ref ? "ref" : pathRefMatch ? "path" : "none"; const calleeRef = matchingEntry.sha || matchingEntry.ref || (pathRefMatch ? pathRefMatch[1] : ""); - core.info(`Resolved callee repo from referenced_workflows: ${calleeRepo} @ ${calleeRef || "(default branch)"}`); + core.info(`Resolved callee repo from referenced_workflows: ${calleeRepo} @ ${calleeRef || "(default branch)"} (source: ${calleeRefSource})`); core.info(` Referenced workflow path: ${matchingEntry.path}`); return { repo: calleeRepo, ref: calleeRef }; } catch (error) { @@ -118,10 +123,15 @@ async function main() { const workflowRef = process.env.GITHUB_WORKFLOW_REF || ""; const currentRepo = process.env.GITHUB_REPOSITORY || ""; + core.info(`GITHUB_WORKFLOW_REF: ${workflowRef || "(not set)"}`); + core.info(`GITHUB_REPOSITORY: ${currentRepo || "(not set)"}`); + core.info(`GITHUB_RUN_ID: ${process.env.GITHUB_RUN_ID || "(not set)"}`); + // GITHUB_WORKFLOW_REF format: owner/repo/.github/workflows/file.yml@ref // The regex captures everything before the third slash segment (i.e., the owner/repo prefix). const repoMatch = workflowRef.match(REPO_PREFIX_RE); const workflowRepo = repoMatch ? repoMatch[1] : ""; + core.info(`Parsed workflow repo from GITHUB_WORKFLOW_REF: ${workflowRepo || "(could not parse)"}`); // Fall back to currentRepo when GITHUB_WORKFLOW_REF cannot be parsed let targetRepo = workflowRepo || currentRepo; @@ -138,6 +148,7 @@ async function main() { // would check out the wrong branch. const refMatch = workflowRef.match(/@(.+)$/); let targetRef = refMatch ? refMatch[1] : ""; + core.info(`Parsed workflow ref from GITHUB_WORKFLOW_REF: ${targetRef || "(none — will use default branch)"}`); // Cross-org workflow_call detection: when GITHUB_WORKFLOW_REF points to the same repo as // GITHUB_REPOSITORY, it means GITHUB_WORKFLOW_REF is resolving to the caller's workflow @@ -150,29 +161,35 @@ async function main() { // (e.g., "push", "issues"), NOT "workflow_call", so we cannot use event_name to detect // this scenario. if (workflowRepo && workflowRepo === currentRepo) { + core.info(`Cross-org workflow_call detected (workflowRepo === currentRepo = ${currentRepo}): falling back to referenced_workflows API`); const resolved = await resolveFromReferencedWorkflows(currentRepo); if (resolved) { targetRepo = resolved.repo; targetRef = resolved.ref || targetRef; + } else { + core.info("referenced_workflows lookup returned no result; keeping current repo as target"); } + } else if (!workflowRepo) { + core.info("Could not parse workflowRepo from GITHUB_WORKFLOW_REF; falling back to GITHUB_REPOSITORY"); + } else { + core.info(`Same-org cross-repo invocation: workflowRepo=${workflowRepo}, currentRepo=${currentRepo}`); } - core.info(`GITHUB_WORKFLOW_REF: ${workflowRef}`); - core.info(`GITHUB_REPOSITORY: ${currentRepo}`); core.info(`Resolved host repo for activation checkout: ${targetRepo}`); - core.info(`Resolved host ref for activation checkout: ${targetRef}`); + core.info(`Resolved host ref for activation checkout: ${targetRef || "(default branch)"}`); if (targetRepo !== currentRepo && targetRepo !== "") { core.info(`Cross-repo invocation detected: platform repo is "${targetRepo}", caller is "${currentRepo}"`); await core.summary.addRaw(`**Activation Checkout**: Checking out platform repo \`${targetRepo}\` @ \`${targetRef}\` (caller: \`${currentRepo}\`)`).write(); } else { - core.info(`Same-repo invocation: checking out ${targetRepo} @ ${targetRef}`); + core.info(`Same-repo invocation: checking out ${targetRepo} @ ${targetRef || "(default branch)"}`); } // Compute the repository name (without owner prefix) for use cases that require // only the repo name, such as actions/create-github-app-token which expects // `repositories` to contain repo names only when `owner` is also provided. const targetRepoName = targetRepo.split("/").at(-1); + core.info(`target_repo=${targetRepo} target_repo_name=${targetRepoName} target_ref=${targetRef || "(default branch)"}`); core.setOutput("target_repo", targetRepo); core.setOutput("target_repo_name", targetRepoName); From 33dab50b53817dc17e1fd56f3fada3fadfdd81b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Apr 2026 01:12:36 +0000 Subject: [PATCH 9/9] Simplify calleeRefSource ternary chain to if-else for readability Agent-Logs-Url: https://github.com/github/gh-aw/sessions/aed19af3-103b-45e4-96bc-9c16ed33477a Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/resolve_host_repo.cjs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/resolve_host_repo.cjs b/actions/setup/js/resolve_host_repo.cjs index 7b1c1e8cd09..1fca3b11dfe 100644 --- a/actions/setup/js/resolve_host_repo.cjs +++ b/actions/setup/js/resolve_host_repo.cjs @@ -104,7 +104,16 @@ async function resolveFromReferencedWorkflows(currentRepo) { // Prefer sha (immutable) over ref (branch/tag can drift) over path-parsed ref. const pathRefMatch = matchingEntry.path.match(/@(.+)$/); - const calleeRefSource = matchingEntry.sha ? "sha" : matchingEntry.ref ? "ref" : pathRefMatch ? "path" : "none"; + let calleeRefSource; + if (matchingEntry.sha) { + calleeRefSource = "sha"; + } else if (matchingEntry.ref) { + calleeRefSource = "ref"; + } else if (pathRefMatch) { + calleeRefSource = "path"; + } else { + calleeRefSource = "none"; + } const calleeRef = matchingEntry.sha || matchingEntry.ref || (pathRefMatch ? pathRefMatch[1] : ""); core.info(`Resolved callee repo from referenced_workflows: ${calleeRepo} @ ${calleeRef || "(default branch)"} (source: ${calleeRefSource})`); core.info(` Referenced workflow path: ${matchingEntry.path}`);