From 2a278818ffc591e1c8c7d71ad3446138df761b40 Mon Sep 17 00:00:00 2001 From: abdellahhanane Date: Wed, 4 Mar 2026 22:37:10 -0500 Subject: [PATCH 1/7] feat(scaffolder): add returnWorkflowRunDetails to github:actions:dispatch action Signed-off-by: abdellahhanane --- .../report.api.md | 9 +- .../githubActionsDispatch.examples.test.ts | 41 ++++++++++ .../actions/githubActionsDispatch.examples.ts | 17 ++++ .../src/actions/githubActionsDispatch.test.ts | 62 ++++++++++++++ .../src/actions/githubActionsDispatch.ts | 82 +++++++++++++++---- 5 files changed, 193 insertions(+), 18 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index b6b2d97ac8..3538529135 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -24,10 +24,13 @@ export function createGithubActionsDispatchAction(options: { workflowId: string; branchOrTagName: string; workflowInputs?: Record | undefined; + returnWorkflowRunDetails?: boolean | undefined; token?: string | undefined; }, { - [x: string]: any; + workflowRunId?: number | undefined; + workflowRunUrl?: string | undefined; + workflowRunHtmlUrl?: string | undefined; }, 'v2' >; @@ -250,8 +253,8 @@ export function createGithubRepoCreateAction(options: { access: string; } | { - team: string; access: string; + team: string; } )[] | undefined; @@ -441,8 +444,8 @@ export function createPublishGithubAction(options: { access: string; } | { - team: string; access: string; + team: string; } )[] | undefined; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts index 5583b745d5..b917efbf28 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts @@ -93,6 +93,7 @@ describe('github:actions:dispatch', () => { ).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', + return_run_details: false, workflow_id: workflowId, ref: branchOrTagName, }); @@ -119,9 +120,49 @@ describe('github:actions:dispatch', () => { ).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', + return_run_details: false, workflow_id: workflowId, ref: branchOrTagName, inputs: workflowInputs, }); }); + + it('should call createWorkflowDispatch with return_run_details when using the returnWorkflowRunDetails example', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: { + workflow_run_id: 123, + run_url: 'https://api.github.com/repos/owner/repo/actions/runs/123', + html_url: 'https://github.com/owner/repo/actions/runs/123', + }, + }); + + const exampleInput = yaml.parse(examples[3].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: { + ...exampleInput, + repoUrl: 'github.com?repo=repo&owner=owner', + }, + }); + await action.handler(ctx); + + expect( + mockOctokit.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith( + expect.objectContaining({ + owner: 'owner', + repo: 'repo', + return_run_details: true, + }), + ); + + expect(ctx.output).toHaveBeenCalledWith('workflowRunId', 123); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunUrl', + 'https://api.github.com/repos/owner/repo/actions/runs/123', + ); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunHtmlUrl', + 'https://github.com/owner/repo/actions/runs/123', + ); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts index 33ad86dd89..2b8baafdbe 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts @@ -71,4 +71,21 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'GitHub Action Workflow returning run details', + example: yaml.stringify({ + steps: [ + { + action: 'github:actions:dispatch', + name: 'Dispatch GitHub Action Workflow and get run ID', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'WORKFLOW_ID', + branchOrTagName: 'main', + returnWorkflowRunDetails: true, + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts index 94a51e5b4e..67f0cf3e82 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts @@ -101,6 +101,7 @@ describe('github:actions:dispatch', () => { repo: 'repo', workflow_id: workflowId, ref: branchOrTagName, + return_run_details: false, }); }); @@ -128,6 +129,67 @@ describe('github:actions:dispatch', () => { workflow_id: workflowId, ref: branchOrTagName, inputs: workflowInputs, + return_run_details: false, }); }); + + it('should call createWorkflowDispatch with return_run_details when returnWorkflowRunDetails is true', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: { + workflow_run_id: 123, + run_url: 'https://api.github.com/repos/owner/repo/actions/runs/123', + html_url: 'https://github.com/owner/repo/actions/runs/123', + }, + }); + + const ctx = Object.assign({}, mockContext, { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'dispatch_workflow', + branchOrTagName: 'main', + returnWorkflowRunDetails: true, + }, + }); + await action.handler(ctx); + + expect( + mockOctokit.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith( + expect.objectContaining({ + owner: 'owner', + repo: 'repo', + workflow_id: 'dispatch_workflow', + ref: 'main', + return_run_details: true, + }), + ); + + expect(ctx.output).toHaveBeenCalledWith('workflowRunId', 123); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunUrl', + 'https://api.github.com/repos/owner/repo/actions/runs/123', + ); + expect(ctx.output).toHaveBeenCalledWith( + 'workflowRunHtmlUrl', + 'https://github.com/owner/repo/actions/runs/123', + ); + }); + + it('should not set outputs when returnWorkflowRunDetails is false', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: undefined, + }); + + const ctx = Object.assign({}, mockContext, { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'dispatch_workflow', + branchOrTagName: 'main', + returnWorkflowRunDetails: false, + }, + }); + await action.handler(ctx); + + expect(ctx.output).not.toHaveBeenCalled(); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index f35cbcc04c..cc2f5f13e8 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { InputError } from '@backstage/errors'; +import { assertError, InputError } from '@backstage/errors'; import { GithubCredentialsProvider, ScmIntegrations, @@ -62,7 +62,14 @@ export function createGithubActionsDispatchAction(options: { z .record(z.string(), { description: - 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10.', + 'Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 25.', + }) + .optional(), + returnWorkflowRunDetails: z => + z + .boolean({ + description: + 'If true, returns the workflow run ID and URLs as action outputs.', }) .optional(), token: z => @@ -73,6 +80,14 @@ export function createGithubActionsDispatchAction(options: { }) .optional(), }, + output: { + workflowRunId: z => + z.number({ description: 'The triggered workflow run ID' }).optional(), + workflowRunUrl: z => + z.string({ description: 'API URL of the workflow run' }).optional(), + workflowRunHtmlUrl: z => + z.string({ description: 'HTML URL of the workflow run' }).optional(), + }, }, async handler(ctx) { const { @@ -80,6 +95,7 @@ export function createGithubActionsDispatchAction(options: { workflowId, branchOrTagName, workflowInputs, + returnWorkflowRunDetails, token: providedToken, } = ctx.input; @@ -106,20 +122,56 @@ export function createGithubActionsDispatchAction(options: { log: ctx.logger, }); - await ctx.checkpoint({ - key: `create.workflow.dispatch.${owner}.${repo}.${workflowId}`, - fn: async () => { - await client.rest.actions.createWorkflowDispatch({ - owner, - repo, - workflow_id: workflowId, - ref: branchOrTagName, - inputs: workflowInputs, - }); + try { + const runDetails = await ctx.checkpoint({ + key: `create.workflow.dispatch.${owner}.${repo}.${workflowId}`, + fn: async () => { + const response = await client.rest.actions.createWorkflowDispatch({ + owner, + repo, + workflow_id: workflowId, + ref: branchOrTagName, + inputs: workflowInputs, + return_run_details: returnWorkflowRunDetails ?? false, + }); - ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); - }, - }); + ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); + + if (returnWorkflowRunDetails && response.data) { + // GitHub's API returns 200 with run details when return_run_details is true. + // @octokit/openapi-types still types this as OctokitResponse because + // it hasn't picked up the updated GitHub OpenAPI spec yet. + // See: https://github.blog/changelog/2026-02-19-workflow-dispatch-api-now-returns-run-ids/ + // This cast can be removed once @octokit/openapi-types includes the updated spec. + // Note: only supported on GitHub.com and GitHub Enterprise Cloud, not GitHub Enterprise Server + const data = response.data as unknown as { + workflow_run_id: number; + run_url: string; + html_url: string; + }; + + return { + workflow_run_id: data.workflow_run_id, + run_url: data.run_url, + workflowRunHtmlUrl: data.html_url, + }; + } + return null; + }, + }); + + if (runDetails) { + ctx.output('workflowRunId', runDetails.workflow_run_id); + ctx.output('workflowRunUrl', runDetails.run_url); + ctx.output('workflowRunHtmlUrl', runDetails.workflowRunHtmlUrl); + } + } catch (e) { + assertError(e); + ctx.logger.warn( + `Failed: dispatching workflow '${workflowId}' on repo: '${repo}', ${e.message}`, + ); + throw e; + } }, }); } From a761a483314ee6a54f33f565d0e2388b732fb4cb Mon Sep 17 00:00:00 2001 From: abdellahhanane Date: Wed, 4 Mar 2026 22:46:49 -0500 Subject: [PATCH 2/7] add changelot Signed-off-by: abdellahhanane --- .changeset/few-clouds-pull.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/few-clouds-pull.md diff --git a/.changeset/few-clouds-pull.md b/.changeset/few-clouds-pull.md new file mode 100644 index 0000000000..52c338ac63 --- /dev/null +++ b/.changeset/few-clouds-pull.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Added optional returnWorkflowRunDetails input to github:actions:dispatch action. When true, exposes workflowRunId, workflowRunUrl, and workflowRunHtmlUrl as outputs using the GitHub API return_run_details parameter. From df21ef6330edda8a6997a1163811a313f441b8ec Mon Sep 17 00:00:00 2001 From: abdellahhanane Date: Thu, 5 Mar 2026 08:21:35 -0500 Subject: [PATCH 3/7] Apply copilot comments and fix CI Signed-off-by: abdellahhanane --- .../githubActionsDispatch.examples.test.ts | 2 -- .../src/actions/githubActionsDispatch.test.ts | 2 -- .../src/actions/githubActionsDispatch.ts | 17 ++++++++++------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts index b917efbf28..077af89c2a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts @@ -93,7 +93,6 @@ describe('github:actions:dispatch', () => { ).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', - return_run_details: false, workflow_id: workflowId, ref: branchOrTagName, }); @@ -120,7 +119,6 @@ describe('github:actions:dispatch', () => { ).toHaveBeenCalledWith({ owner: 'owner', repo: 'repo', - return_run_details: false, workflow_id: workflowId, ref: branchOrTagName, inputs: workflowInputs, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts index 67f0cf3e82..fc8cfcce0b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts @@ -101,7 +101,6 @@ describe('github:actions:dispatch', () => { repo: 'repo', workflow_id: workflowId, ref: branchOrTagName, - return_run_details: false, }); }); @@ -129,7 +128,6 @@ describe('github:actions:dispatch', () => { workflow_id: workflowId, ref: branchOrTagName, inputs: workflowInputs, - return_run_details: false, }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index cc2f5f13e8..c882869085 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -126,14 +126,17 @@ export function createGithubActionsDispatchAction(options: { const runDetails = await ctx.checkpoint({ key: `create.workflow.dispatch.${owner}.${repo}.${workflowId}`, fn: async () => { - const response = await client.rest.actions.createWorkflowDispatch({ + const dispatchParams = { owner, repo, workflow_id: workflowId, ref: branchOrTagName, inputs: workflowInputs, - return_run_details: returnWorkflowRunDetails ?? false, - }); + ...(returnWorkflowRunDetails ? { return_run_details: true } : {}), + }; + const response = await client.rest.actions.createWorkflowDispatch( + dispatchParams, + ); ctx.logger.info(`Workflow ${workflowId} dispatched successfully`); @@ -151,8 +154,8 @@ export function createGithubActionsDispatchAction(options: { }; return { - workflow_run_id: data.workflow_run_id, - run_url: data.run_url, + workflowRunId: data.workflow_run_id, + workflowRunUrl: data.run_url, workflowRunHtmlUrl: data.html_url, }; } @@ -161,8 +164,8 @@ export function createGithubActionsDispatchAction(options: { }); if (runDetails) { - ctx.output('workflowRunId', runDetails.workflow_run_id); - ctx.output('workflowRunUrl', runDetails.run_url); + ctx.output('workflowRunId', runDetails.workflowRunId); + ctx.output('workflowRunUrl', runDetails.workflowRunUrl); ctx.output('workflowRunHtmlUrl', runDetails.workflowRunHtmlUrl); } } catch (e) { From fba21cfbcbcb2e61211c377ea498056328ddd43f Mon Sep 17 00:00:00 2001 From: abdellahhanane Date: Thu, 5 Mar 2026 08:22:56 -0500 Subject: [PATCH 4/7] Add return_run_details to accept.txt Signed-off-by: abdellahhanane --- .github/vale/config/vocabularies/Backstage/accept.txt | 1 + plugins/scaffolder-backend-module-github/report.api.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 11df7fc1a0..cbf3130537 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -581,3 +581,4 @@ resizable enums LLMs cleye +return_run_details diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 3538529135..9e930a7866 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -253,8 +253,8 @@ export function createGithubRepoCreateAction(options: { access: string; } | { - access: string; team: string; + access: string; } )[] | undefined; @@ -444,8 +444,8 @@ export function createPublishGithubAction(options: { access: string; } | { - access: string; team: string; + access: string; } )[] | undefined; From ed5d1ea15c1da828c7dd0f87bd11c7e18ca9bedf Mon Sep 17 00:00:00 2001 From: abdellahhanane Date: Mon, 9 Mar 2026 10:29:10 -0400 Subject: [PATCH 5/7] Apply PR comments Signed-off-by: abdellahhanane --- .changeset/few-clouds-pull.md | 2 +- .github/vale/config/vocabularies/Backstage/accept.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/few-clouds-pull.md b/.changeset/few-clouds-pull.md index 52c338ac63..4f8a67389b 100644 --- a/.changeset/few-clouds-pull.md +++ b/.changeset/few-clouds-pull.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-github': patch --- -Added optional returnWorkflowRunDetails input to github:actions:dispatch action. When true, exposes workflowRunId, workflowRunUrl, and workflowRunHtmlUrl as outputs using the GitHub API return_run_details parameter. +Added optional `returnWorkflowRunDetails` input to `github:actions:dispatch` action. When true, exposes `workflowRunId`, `workflowRunUrl`, and `workflowRunHtmlUrl` as outputs using the GitHub API `return_run_details` parameter. diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index cbf3130537..11df7fc1a0 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -581,4 +581,3 @@ resizable enums LLMs cleye -return_run_details From 5fbe51d71360dcf5411355d82f77a28a119861cd Mon Sep 17 00:00:00 2001 From: abdellahhanane Date: Wed, 11 Mar 2026 21:27:58 -0400 Subject: [PATCH 6/7] Add log for the workflow run url Signed-off-by: abdellahhanane --- .../src/actions/githubActionsDispatch.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index c882869085..4cc10235a1 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -167,6 +167,8 @@ export function createGithubActionsDispatchAction(options: { ctx.output('workflowRunId', runDetails.workflowRunId); ctx.output('workflowRunUrl', runDetails.workflowRunUrl); ctx.output('workflowRunHtmlUrl', runDetails.workflowRunHtmlUrl); + + ctx.logger.info(`Workflow run url: ${runDetails.workflowRunHtmlUrl}`); } } catch (e) { assertError(e); From ad0adafaea79dc7a04f4dbe5086b53fb9ece1f1b Mon Sep 17 00:00:00 2001 From: abdellahhanane Date: Thu, 12 Mar 2026 13:41:43 -0400 Subject: [PATCH 7/7] Add check on workflowRunHtmlUrl is not undefined Signed-off-by: abdellahhanane --- .../src/actions/githubActionsDispatch.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index 4cc10235a1..00dd72a9e3 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -168,7 +168,11 @@ export function createGithubActionsDispatchAction(options: { ctx.output('workflowRunUrl', runDetails.workflowRunUrl); ctx.output('workflowRunHtmlUrl', runDetails.workflowRunHtmlUrl); - ctx.logger.info(`Workflow run url: ${runDetails.workflowRunHtmlUrl}`); + if (runDetails.workflowRunHtmlUrl) { + ctx.logger.info( + `Workflow run url: ${runDetails.workflowRunHtmlUrl}`, + ); + } } } catch (e) { assertError(e);