From 2f3576c519a25caa54d189509ce224cb38ec9af9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:38:57 +0000 Subject: [PATCH 01/10] Improve warning message formatting for workflow run timeout --- src/main.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main.ts b/src/main.ts index 81513c7..4478d2d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -98,10 +98,12 @@ async function run(): Promise { const startTime = Date.now() while (runStatus === 'in_progress' || runStatus === 'queued' || runStatus === 'waiting') { if ((Date.now() - startTime) / 1000 > timeoutSeconds) { - core.warning(`⚠️ Workflow run did not complete within ${timeoutSeconds} seconds, timing out.\nNote: The workflow is still running but we have stopped waiting. You can check the run status here: ${dispatchResp.data.html_url}`) + core.warning( + `⚠️ Workflow run did not complete within ${timeoutSeconds} seconds, timing out.\nNote: The workflow is still running but we have stopped waiting. You can check the run status here: ${dispatchResp.data.html_url}`, + ) break } - + await new Promise((resolve) => setTimeout(resolve, 5000)) // Wait for 5 seconds before polling again const { data: runData } = await octokit.request( @@ -111,7 +113,7 @@ async function run(): Promise { core.info(`🔄 Current run status: ${runStatus}`) } - if (runStatus === 'completed') { + if (runStatus === 'completed') { core.info('✅ Workflow run completed successfully!') } else { core.warning(`⚠️ Workflow run completed with status: ${runStatus}`) From 3407b6547710d1ba6ed9b134a31b1bf8ec94a414 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:38:59 +0000 Subject: [PATCH 02/10] project chores --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f73b903..6a04d68 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,17 @@ The workflow must be configured for this event type e.g. `on: [workflow_dispatch This allows you to chain workflows, the classic use case is have a CI build workflow, trigger a CD release/deploy workflow when it completes. Allowing you to maintain separate workflows for CI and CD, and pass data between them as required. -**2026 Update**: We finally have a way to get the details of the triggered workflow run, including the run ID and URL, which means we can now poll for the run status and wait for it to complete if required. This is a common ask and I'm glad to have added this feature after nearly 6 years! - ![Screenshot the logs of a workflow run](./etc/screen.png) For details of the `workflow_dispatch` even see [this blog post introducing this type of trigger](https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/) -_Note 1._ GitHub now has a native way to chain workflows called "reusable workflows". See the docs on [reusing workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows). This approach is somewhat different from workflow_dispatch but it's worth keeping in mind. +Note, GitHub now has a native way to chain workflows called "reusable workflows". See the docs on [reusing workflows](https://docs.github.com/en/actions/using-workflows/reusing-workflows). This approach is somewhat different from workflow_dispatch but it's worth keeping in mind. + +The GitHub UI will report flows triggered by this action as "manually triggered" this might seem confusing at first, but it makes sense when you remember that the workflow_dispatch event is designed to be triggered manually by a user, and this action is simulating that manual trigger. -_Note 2._ The GitHub UI will report flows triggered by this action as "manually triggered" even though they have been run programmatically via another workflow and the API. +## 2026 Update -_Note 3._ If you want to reference the target workflow by ID, you will need to list them with the following REST API call `curl https://api.github.com/repos/{{owner}}/{{repo}}/actions/workflows -H "Authorization: token {{pat-token}}"` +We finally have a way to get the details of the triggered workflow run, including the run ID and URL, which means we can now poll for the run status and wait for it to complete if required. This is a common ask and I'm glad to have added this feature after nearly 6 years! ## Action Inputs @@ -33,6 +33,8 @@ workflow: my-workflow.yaml workflow: 1218419 ``` +> _Note:_ If you want to reference the target workflow by ID, you will need to list the IDs with a REST API call `curl https://api.github.com/repos/{{owner}}/{{repo}}/actions/workflows -H "Authorization: token {{pat-token}}"` + ### `inputs` **Optional.** The inputs to pass to the workflow (if any are configured), this must be a JSON encoded string, e.g. `{ "myInput": "foobar" }` From e7a53f2b10eb4fd00974ff9678035bc9115e42f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:39:00 +0000 Subject: [PATCH 03/10] Use alternate `ref` default for PRs (#79) --- action.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yaml b/action.yaml index 166ae00..23f3bef 100644 --- a/action.yaml +++ b/action.yaml @@ -15,6 +15,7 @@ inputs: ref: description: 'The reference can be a branch, tag, or a commit SHA' required: false + default: ${{ github.head_ref || github.ref }} repo: description: 'Repo owner & name, slash separated, only set if invoking a workflow in a different repo' required: false From e20a59c7cacf261d8c40e44752494576dbafb715 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:39:02 +0000 Subject: [PATCH 04/10] Use alternate `ref` default for PRs (#79) --- src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index 4478d2d..fa073b2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -31,7 +31,7 @@ async function run(): Promise { // Optional inputs, with defaults const token = core.getInput('token') - const ref = core.getInput('ref') || github.context.ref + const ref = core.getInput('ref') const [owner, repo] = core.getInput('repo') ? core.getInput('repo').split('/') : [github.context.repo.owner, github.context.repo.repo] From dba81fb72e4a6f280eb43a1f10e720f456346f20 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:39:03 +0000 Subject: [PATCH 05/10] Improvements: sync-status, error handling, CI test coverage & path filters (#84) * Add Copilot instructions and review document; refactor logging and build target * Update workflow configurations: add ref and wait-for-completion to echo 2; reduce sleep time in echo 2 * testing inputs * Update Node.js version and simplify inputs for Message Echo 1 workflow * Add error handling for JSON parsing of inputs and improve timeout warnings * Remove unused VSCode settings and delete outdated review document * Update workflow to use fallback for ref in echo workflows * Add sync-status input and enhance error handling for workflow run completion * testing * Enhance workflow completion messages and add final conclusion checks for run status * Add sync-status input to action and update description for workflow status handling * Remove debug echo statement and add comment for workflow run conclusion handling * Reduce sleep duration in echo job and simplify workflow conclusion message * Refactor workflow files for improved clarity and functionality; update echo job names, enhance dispatch tests, and sync action status with triggered workflows. * ci: add path filters to skip builds on docs-only changes * Merge branch 'main' into tweaks * fix: remove fallback for ref input in workflow run function --- action.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/action.yaml b/action.yaml index 23f3bef..c767892 100644 --- a/action.yaml +++ b/action.yaml @@ -27,6 +27,10 @@ inputs: description: 'Maximum time in seconds to wait for the workflow run to complete before timing out (only applies if wait-for-completion is true)' required: false default: 900 + sync-status: + description: 'Whether to set the status of this action to failed if the triggered workflow run fails, or is cancelled. Only applies if wait-for-completion is true.' + required: false + default: false outputs: runId: From 7829ea57c8e3644cc89dc7393f85ce16667673dc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:39:05 +0000 Subject: [PATCH 06/10] Improvements: sync-status, error handling, CI test coverage & path filters (#84) * Add Copilot instructions and review document; refactor logging and build target * Update workflow configurations: add ref and wait-for-completion to echo 2; reduce sleep time in echo 2 * testing inputs * Update Node.js version and simplify inputs for Message Echo 1 workflow * Add error handling for JSON parsing of inputs and improve timeout warnings * Remove unused VSCode settings and delete outdated review document * Update workflow to use fallback for ref in echo workflows * Add sync-status input and enhance error handling for workflow run completion * testing * Enhance workflow completion messages and add final conclusion checks for run status * Add sync-status input to action and update description for workflow status handling * Remove debug echo statement and add comment for workflow run conclusion handling * Reduce sleep duration in echo job and simplify workflow conclusion message * Refactor workflow files for improved clarity and functionality; update echo job names, enhance dispatch tests, and sync action status with triggered workflows. * ci: add path filters to skip builds on docs-only changes * Merge branch 'main' into tweaks * fix: remove fallback for ref input in workflow run function --- src/main.ts | 48 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/main.ts b/src/main.ts index fa073b2..10c8d98 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,9 +18,9 @@ type Workflow = { path: string } -// +// ============================================================================= // Main task function (async wrapper) -// +// ============================================================================= async function run(): Promise { core.info(`🏃 Workflow Dispatch Action v${PackageJSON.version}`) try { @@ -40,7 +40,11 @@ async function run(): Promise { let inputs = {} const inputsJson = core.getInput('inputs') if (inputsJson) { - inputs = JSON.parse(inputsJson) + try { + inputs = JSON.parse(inputsJson) + } catch (e) { + core.error(`Failed to parse 'inputs' JSON string: ${e instanceof Error ? e.message : String(e)}`) + } } // Get octokit client for making API calls @@ -51,8 +55,6 @@ async function run(): Promise { octokit.rest.actions.listRepoWorkflows.endpoint.merge({ owner, repo, - ref, - inputs, }), ) @@ -73,10 +75,10 @@ async function run(): Promise { if (!foundWorkflow) throw new Error(`Unable to find workflow '${workflowRef}' in ${owner}/${repo} 😥`) - console.log(`🔎 Found workflow, id: ${foundWorkflow.id}, name: ${foundWorkflow.name}, path: ${foundWorkflow.path}`) + core.info(`🔎 Found workflow, id: ${foundWorkflow.id}, name: ${foundWorkflow.name}, path: ${foundWorkflow.path}`) // Call workflow_dispatch API - console.log('🚀 Calling GitHub API to dispatch workflow...') + core.info('🚀 Calling GitHub API to dispatch workflow...') const dispatchResp = await octokit.request( `POST /repos/${owner}/${repo}/actions/workflows/${foundWorkflow.id}/dispatches`, { @@ -91,16 +93,20 @@ async function run(): Promise { // Handle wait for completion const waitForCompletion = core.getInput('wait-for-completion') === 'true' + const syncStatus = core.getInput('sync-status') === 'true' const timeoutSeconds = parseInt(core.getInput('wait-timeout-seconds') || '900', 10) // Default to 15 minutes + let runStatus = 'in_progress' + + // Polling loop to check workflow run status until it completes or times out if (waitForCompletion) { core.info(`⏳ Waiting for workflow run to complete with a timeout of ${timeoutSeconds} seconds...`) - let runStatus = 'in_progress' const startTime = Date.now() while (runStatus === 'in_progress' || runStatus === 'queued' || runStatus === 'waiting') { if ((Date.now() - startTime) / 1000 > timeoutSeconds) { core.warning( `⚠️ Workflow run did not complete within ${timeoutSeconds} seconds, timing out.\nNote: The workflow is still running but we have stopped waiting. You can check the run status here: ${dispatchResp.data.html_url}`, ) + runStatus = 'timed_out' break } @@ -114,7 +120,9 @@ async function run(): Promise { } if (runStatus === 'completed') { - core.info('✅ Workflow run completed successfully!') + core.info('✅ Workflow run completed, the final status can be found in the workflow run details.') + } else if (runStatus === 'timed_out') { + core.warning(`⚠️ Workflow run did not complete within the timeout period.`) } else { core.warning(`⚠️ Workflow run completed with status: ${runStatus}`) } @@ -124,6 +132,24 @@ async function run(): Promise { core.setOutput('runUrl', dispatchResp.data.run_url) core.setOutput('runUrlHtml', dispatchResp.data.html_url) core.setOutput('workflowId', foundWorkflow.id) + + // Sync the status of this action with the triggered workflow run if requested + if (syncStatus && waitForCompletion) { + // Get the final conclusion of the workflow run if we were waiting for completion + const { data: finalRunData } = await octokit.request( + `GET /repos/${owner}/${repo}/actions/runs/${dispatchResp.data.workflow_run_id}`, + ) + const conclusion = finalRunData.conclusion + + // Set this action to failed if the triggered workflow run failed or was cancelled + if (conclusion === 'failure') { + core.setFailed(`Workflow run failed. Check the run details here: ${dispatchResp.data.html_url}`) + } else if (conclusion === 'cancelled') { + core.setFailed(`Workflow run was cancelled. Check the run details here: ${dispatchResp.data.html_url}`) + } else { + core.info(`🎉 Workflow conclusion: ${conclusion}`) + } + } } catch (error) { const e = error as Error @@ -183,7 +209,7 @@ async function validateSubscription(): Promise { } } -// +// ============================================================================= // Call the main task run function -// +// ============================================================================= run() From b0f92503491fd1edb02ded45d2b17838b64a8cf4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:39:10 +0000 Subject: [PATCH 07/10] fix: apply code build script --- dist/index.js | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/dist/index.js b/dist/index.js index 4423d7d..60e4d93 100644 --- a/dist/index.js +++ b/dist/index.js @@ -39297,20 +39297,22 @@ async function run() { await validateSubscription(); const workflowRef = getInput("workflow"); const token = getInput("token"); - const ref = getInput("ref") || context2.ref; + const ref = getInput("ref"); const [owner, repo] = getInput("repo") ? getInput("repo").split("/") : [context2.repo.owner, context2.repo.repo]; let inputs = {}; const inputsJson = getInput("inputs"); if (inputsJson) { - inputs = JSON.parse(inputsJson); + try { + inputs = JSON.parse(inputsJson); + } catch (e) { + error(`Failed to parse 'inputs' JSON string: ${e instanceof Error ? e.message : String(e)}`); + } } const octokit = getOctokit(token); const workflows = await octokit.paginate( octokit.rest.actions.listRepoWorkflows.endpoint.merge({ owner, - repo, - ref, - inputs + repo }) ); debug("### START List Workflows response data"); @@ -39321,8 +39323,8 @@ async function run() { workflow.path == workflowRef; }); if (!foundWorkflow) throw new Error(`Unable to find workflow '${workflowRef}' in ${owner}/${repo} \u{1F625}`); - console.log(`\u{1F50E} Found workflow, id: ${foundWorkflow.id}, name: ${foundWorkflow.name}, path: ${foundWorkflow.path}`); - console.log("\u{1F680} Calling GitHub API to dispatch workflow..."); + info(`\u{1F50E} Found workflow, id: ${foundWorkflow.id}, name: ${foundWorkflow.name}, path: ${foundWorkflow.path}`); + info("\u{1F680} Calling GitHub API to dispatch workflow..."); const dispatchResp = await octokit.request( `POST /repos/${owner}/${repo}/actions/workflows/${foundWorkflow.id}/dispatches`, { @@ -39334,15 +39336,19 @@ async function run() { info(`\u{1F3C6} API response status: ${dispatchResp.status}`); info(`\u{1F310} Run URL: ${dispatchResp.data.html_url}`); const waitForCompletion = getInput("wait-for-completion") === "true"; + const syncStatus = getInput("sync-status") === "true"; const timeoutSeconds = parseInt(getInput("wait-timeout-seconds") || "900", 10); + let runStatus = "in_progress"; if (waitForCompletion) { info(`\u23F3 Waiting for workflow run to complete with a timeout of ${timeoutSeconds} seconds...`); - let runStatus = "in_progress"; const startTime = Date.now(); while (runStatus === "in_progress" || runStatus === "queued" || runStatus === "waiting") { if ((Date.now() - startTime) / 1e3 > timeoutSeconds) { - warning(`\u26A0\uFE0F Workflow run did not complete within ${timeoutSeconds} seconds, timing out. -Note: The workflow is still running but we have stopped waiting. You can check the run status here: ${dispatchResp.data.html_url}`); + warning( + `\u26A0\uFE0F Workflow run did not complete within ${timeoutSeconds} seconds, timing out. +Note: The workflow is still running but we have stopped waiting. You can check the run status here: ${dispatchResp.data.html_url}` + ); + runStatus = "timed_out"; break; } await new Promise((resolve) => setTimeout(resolve, 5e3)); @@ -39353,7 +39359,9 @@ Note: The workflow is still running but we have stopped waiting. You can check t info(`\u{1F504} Current run status: ${runStatus}`); } if (runStatus === "completed") { - info("\u2705 Workflow run completed successfully!"); + info("\u2705 Workflow run completed, the final status can be found in the workflow run details."); + } else if (runStatus === "timed_out") { + warning(`\u26A0\uFE0F Workflow run did not complete within the timeout period.`); } else { warning(`\u26A0\uFE0F Workflow run completed with status: ${runStatus}`); } @@ -39362,6 +39370,19 @@ Note: The workflow is still running but we have stopped waiting. You can check t setOutput("runUrl", dispatchResp.data.run_url); setOutput("runUrlHtml", dispatchResp.data.html_url); setOutput("workflowId", foundWorkflow.id); + if (syncStatus && waitForCompletion) { + const { data: finalRunData } = await octokit.request( + `GET /repos/${owner}/${repo}/actions/runs/${dispatchResp.data.workflow_run_id}` + ); + const conclusion = finalRunData.conclusion; + if (conclusion === "failure") { + setFailed(`Workflow run failed. Check the run details here: ${dispatchResp.data.html_url}`); + } else if (conclusion === "cancelled") { + setFailed(`Workflow run was cancelled. Check the run details here: ${dispatchResp.data.html_url}`); + } else { + info(`\u{1F389} Workflow conclusion: ${conclusion}`); + } + } } catch (error2) { const e = error2; if (e.message.endsWith("a disabled workflow")) { From 418cab8abef93565d6eb69c495d012ecb772515d Mon Sep 17 00:00:00 2001 From: Raj-StepSecurity Date: Tue, 21 Apr 2026 16:15:48 +0530 Subject: [PATCH 08/10] conflicted commits cherry-picked --- .github/workflows/build-test.yaml | 230 +++++++++++++++++++++++++++--- .github/workflows/echo-2.yaml | 4 +- .github/workflows/echo-3.yaml | 16 +-- dist/index.js | 13 +- package-lock.json | 4 +- package.json | 11 +- src/main.ts | 18 +-- 7 files changed, 241 insertions(+), 55 deletions(-) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 930905f..f754b15 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -1,38 +1,236 @@ -name: Build & Test +name: 'CI: Lint, Build & Test' on: push: branches: [main] + paths: + - 'src/**' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - 'eslint.config.mjs' + - 'action.yaml' + - 'dist/**' + - '.github/workflows/**' + pull_request: + branches: [main] + paths: + - 'src/**' + - 'package.json' + - 'package-lock.json' + - 'tsconfig.json' + - 'eslint.config.mjs' + - 'action.yaml' + - 'dist/**' + - '.github/workflows/**' workflow_dispatch: -# permissions: -# contents: write - jobs: + # =========================================================================== + # Build & lint — all test jobs depend on this + # =========================================================================== build: + name: Build & Lint runs-on: ubuntu-latest steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 with: - egress-policy: audit + node-version: '24.x' + - run: npm ci + - run: npm run lint + - run: npm run build + + # =========================================================================== + # Test: Dispatch a workflow by its display name, with inputs + # =========================================================================== + test-dispatch-by-name: + name: 'Test: Dispatch by name' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 - - name: Check out repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Dispatch echo-1 by name + id: dispatch + uses: ./ + with: + workflow: Message Echo 1 + inputs: '{"message": "hello from name test"}' + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} - - name: Build with esbuild + - name: Assert outputs are set + env: + RUN_ID: ${{ steps.dispatch.outputs.runId }} + WORKFLOW_ID: ${{ steps.dispatch.outputs.workflowId }} + RUN_URL: ${{ steps.dispatch.outputs.runUrl }} + RUN_URL_HTML: ${{ steps.dispatch.outputs.runUrlHtml }} run: | - npm install - npm run build + echo "runId=$RUN_ID workflowId=$WORKFLOW_ID" + echo "runUrl=$RUN_URL" + echo "runUrlHtml=$RUN_URL_HTML" + [[ -n "$RUN_ID" ]] || { echo "FAIL: runId empty"; exit 1; } + [[ -n "$WORKFLOW_ID" ]] || { echo "FAIL: workflowId empty"; exit 1; } + [[ -n "$RUN_URL" ]] || { echo "FAIL: runUrl empty"; exit 1; } + [[ -n "$RUN_URL_HTML" ]] || { echo "FAIL: runUrlHtml empty"; exit 1; } - - name: Invoke echo 1 workflow using this action + # =========================================================================== + # Test: Dispatch a workflow by its filename (short form) + # =========================================================================== + test-dispatch-by-filename: + name: 'Test: Dispatch by filename' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Dispatch echo-2 by filename uses: ./ with: - workflow: Message Echo 1 - inputs: '{"message": "blah blah"}' + workflow: echo-2.yaml + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} - - name: Invoke echo 2 workflow using this action + # =========================================================================== + # Test: Dispatch a workflow by its full path + # =========================================================================== + test-dispatch-by-path: + name: 'Test: Dispatch by full path' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Dispatch echo-2 by full path + uses: ./ + with: + workflow: .github/workflows/echo-2.yaml + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + + # =========================================================================== + # Test: Dispatch a workflow by its numeric ID + # =========================================================================== + test-dispatch-by-id: + name: 'Test: Dispatch by ID' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + # NOTE: This ID is specific to this repo's echo-1 workflow. + # If the workflow file is deleted & recreated, this ID will change. + - name: Dispatch echo-1 by numeric ID + uses: ./ + with: + workflow: '1854247' + inputs: '{"message": "dispatched by id"}' + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + + # =========================================================================== + # Test: Wait for completion on a slow workflow (success case) + # =========================================================================== + test-wait-completion: + name: 'Test: Wait for completion' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Dispatch echo-2 and wait for it to finish uses: ./ with: workflow: echo-2.yaml + wait-for-completion: true + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + # =========================================================================== + # Test: sync-status mirrors a successful workflow conclusion + # =========================================================================== + test-sync-status-success: + name: 'Test: Sync status (success)' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Dispatch echo-1 with wait + sync-status + uses: ./ + with: + workflow: Message Echo 1 + inputs: '{"message": "sync status success test"}' + wait-for-completion: true + sync-status: true + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + + # =========================================================================== + # Test: sync-status propagates a failing workflow as a failure + # =========================================================================== + test-sync-status-failure: + name: 'Test: Sync status (failure)' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Dispatch echo-3 (always fails) with sync-status + id: dispatch-fail + continue-on-error: true + uses: ./ + with: + workflow: echo-3.yaml + wait-for-completion: true + sync-status: true + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + + - name: Assert action reported failure + run: | + if [[ "${{ steps.dispatch-fail.outcome }}" != "failure" ]]; then + echo "FAIL: Expected action to fail when sync-status mirrors a failing workflow" + exit 1 + fi + echo "PASS: Action correctly propagated failure" + + # =========================================================================== + # Test: Invalid workflow reference produces an error + # =========================================================================== + test-invalid-workflow: + name: 'Test: Invalid workflow name' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Dispatch a workflow that does not exist + id: bad-workflow + continue-on-error: true + uses: ./ + with: + workflow: this-workflow-does-not-exist.yaml + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} + + - name: Assert action failed + run: | + if [[ "${{ steps.bad-workflow.outcome }}" != "failure" ]]; then + echo "FAIL: Expected action to fail for non-existent workflow" + exit 1 + fi + echo "PASS: Action correctly failed for invalid workflow reference" + + # =========================================================================== + # Test: Wait timeout fires before a slow workflow finishes + # =========================================================================== + test-wait-timeout: + name: 'Test: Wait timeout' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + # echo-2 sleeps for 11s — a 1-second timeout will trigger after the + # first poll cycle (~5s). The action warns but does NOT fail on timeout. + - name: Dispatch echo-2 with 1-second timeout + uses: ./ + with: + workflow: echo-2.yaml + wait-for-completion: true + wait-timeout-seconds: 1 + ref: ${{ github.event.pull_request.head.ref || github.ref_name }} diff --git a/.github/workflows/echo-2.yaml b/.github/workflows/echo-2.yaml index 0969420..0df036e 100644 --- a/.github/workflows/echo-2.yaml +++ b/.github/workflows/echo-2.yaml @@ -20,7 +20,7 @@ jobs: with: egress-policy: audit - - name: Echo message slowly with a wait + - name: Echo message with a wait run: | - sleep 125 + sleep 11 echo '${{ inputs.message }}' diff --git a/.github/workflows/echo-3.yaml b/.github/workflows/echo-3.yaml index fd1db3d..d600dcb 100644 --- a/.github/workflows/echo-3.yaml +++ b/.github/workflows/echo-3.yaml @@ -1,16 +1,12 @@ name: Message Echo 3 -# A version using workflow_call for investigation purposes - -on: - workflow_call: +on: + workflow_dispatch: inputs: message: required: false - default: "this is echo 3" - type: string - description: "Message to echo" - + default: 'this is echo 3' + description: 'Message to echo' permissions: contents: read @@ -23,5 +19,5 @@ jobs: with: egress-policy: audit - - name: Echo message - run: echo '${{ inputs.message }}' \ No newline at end of file + - name: Intentional failure + run: exit 1 diff --git a/dist/index.js b/dist/index.js index 60e4d93..9edc82d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -39288,7 +39288,7 @@ function getOctokit(token, options, ...additionalPlugins) { var fs3 = __toESM(require("fs")); // package.json -var version = "1.3.0"; +var version = "1.3.1"; // src/main.ts async function run() { @@ -39405,8 +39405,7 @@ async function validateSubscription() { info(""); info("\x1B[1;36mStepSecurity Maintained Action\x1B[0m"); info(`Secure drop-in replacement for ${upstream}`); - if (repoPrivate === false) - info("\x1B[32m\u2713 Free for public repositories\x1B[0m"); + if (repoPrivate === false) info("\x1B[32m\u2713 Free for public repositories\x1B[0m"); info(`\x1B[36mLearn more:\x1B[0m ${docsUrl}`); info(""); if (repoPrivate === false) return; @@ -39421,12 +39420,8 @@ async function validateSubscription() { ); } catch (error2) { if (isAxiosError2(error2) && error2.response?.status === 403) { - error( - `\x1B[1;31mThis action requires a StepSecurity subscription for private repositories.\x1B[0m` - ); - error( - `\x1B[31mLearn how to enable a subscription: ${docsUrl}\x1B[0m` - ); + error(`\x1B[1;31mThis action requires a StepSecurity subscription for private repositories.\x1B[0m`); + error(`\x1B[31mLearn how to enable a subscription: ${docsUrl}\x1B[0m`); process.exit(1); } info("Timeout or API not reachable. Continuing to next step."); diff --git a/package-lock.json b/package-lock.json index 0a6c93a..8c82511 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "workflow-dispatch", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "workflow-dispatch", - "version": "1.3.0", + "version": "1.3.1", "license": "MIT", "devDependencies": { "@actions/core": "^3.0.0", diff --git a/package.json b/package.json index 934a405..2225dd1 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,20 @@ { "name": "workflow-dispatch", - "version": "1.3.0", + "version": "1.3.1", + "author": "step-security", + "license": "MIT", "description": "Trigger running GitHub Actions workflows", "main": "dist/index.js", "scripts": { - "build": "esbuild src/main.ts --bundle --platform=node --target=node22 --outfile=dist/index.js", - "lint": "eslint src/" + "build": "esbuild src/main.ts --bundle --platform=node --target=node24 --outfile=dist/index.js", + "lint": "eslint src/ --ext .ts && prettier --check src/", + "lint-fix": "eslint src/ --fix", + "format": "prettier --write src/" }, "keywords": [ "github", "actions" ], - "license": "MIT", "devDependencies": { "@actions/core": "^3.0.0", "@actions/github": "^9.0.0", diff --git a/src/main.ts b/src/main.ts index 10c8d98..f9bc4d3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -173,36 +173,30 @@ async function validateSubscription(): Promise { const upstream = 'benc-uk/workflow-dispatch' const action = process.env.GITHUB_ACTION_REPOSITORY - const docsUrl = - 'https://docs.stepsecurity.io/actions/stepsecurity-maintained-actions' + const docsUrl = 'https://docs.stepsecurity.io/actions/stepsecurity-maintained-actions' core.info('') core.info('\u001b[1;36mStepSecurity Maintained Action\u001b[0m') core.info(`Secure drop-in replacement for ${upstream}`) - if (repoPrivate === false) - core.info('\u001b[32m\u2713 Free for public repositories\u001b[0m') + if (repoPrivate === false) core.info('\u001b[32m\u2713 Free for public repositories\u001b[0m') core.info(`\u001b[36mLearn more:\u001b[0m ${docsUrl}`) core.info('') if (repoPrivate === false) return const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com' - const body: Record = {action: action || ''} + const body: Record = { action: action || '' } if (serverUrl !== 'https://github.com') body.ghes_server = serverUrl try { await axios.post( `https://agent.api.stepsecurity.io/v1/github/${process.env.GITHUB_REPOSITORY}/actions/maintained-actions-subscription`, body, - {timeout: 3000} + { timeout: 3000 }, ) } catch (error) { if (isAxiosError(error) && error.response?.status === 403) { - core.error( - `\u001b[1;31mThis action requires a StepSecurity subscription for private repositories.\u001b[0m` - ) - core.error( - `\u001b[31mLearn how to enable a subscription: ${docsUrl}\u001b[0m` - ) + core.error(`\u001b[1;31mThis action requires a StepSecurity subscription for private repositories.\u001b[0m`) + core.error(`\u001b[31mLearn how to enable a subscription: ${docsUrl}\u001b[0m`) process.exit(1) } core.info('Timeout or API not reachable. Continuing to next step.') From 0eaa30aadc389fd9b934673ef289fcaaea5b50e7 Mon Sep 17 00:00:00 2001 From: Raj-StepSecurity Date: Tue, 21 Apr 2026 16:16:45 +0530 Subject: [PATCH 09/10] secure workflow applied over updated build-test --- .github/workflows/build-test.yaml | 72 ++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index f754b15..404cd40 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -33,8 +33,13 @@ jobs: name: Build & Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-node@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: '24.x' - run: npm ci @@ -49,7 +54,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dispatch echo-1 by name id: dispatch @@ -82,7 +92,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dispatch echo-2 by filename uses: ./ @@ -98,7 +113,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dispatch echo-2 by full path uses: ./ @@ -114,7 +134,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # NOTE: This ID is specific to this repo's echo-1 workflow. # If the workflow file is deleted & recreated, this ID will change. @@ -133,7 +158,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dispatch echo-2 and wait for it to finish uses: ./ @@ -150,7 +180,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dispatch echo-1 with wait + sync-status uses: ./ @@ -169,7 +204,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dispatch echo-3 (always fails) with sync-status id: dispatch-fail @@ -197,7 +237,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dispatch a workflow that does not exist id: bad-workflow @@ -223,7 +268,12 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # echo-2 sleeps for 11s — a 1-second timeout will trigger after the # first poll cycle (~5s). The action warns but does NOT fail on timeout. From 744201ba631b9496322e6413d03ad260a1b1f986 Mon Sep 17 00:00:00 2001 From: Raj-StepSecurity Date: Tue, 21 Apr 2026 16:19:17 +0530 Subject: [PATCH 10/10] build test updated --- .github/workflows/build-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 404cd40..ba9ba7a 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -146,7 +146,7 @@ jobs: - name: Dispatch echo-1 by numeric ID uses: ./ with: - workflow: '1854247' + workflow: '78181717' inputs: '{"message": "dispatched by id"}' ref: ${{ github.event.pull_request.head.ref || github.ref_name }}