Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,24 @@ This option is also left for backwards compatibility with older versions where t

### `sync-status`

**Optional.** Set to `'true'` to sync the status of this action with the triggered workflow run. If the triggered workflow run fails or is cancelled, this action will also be set to failed. This only applies if `wait-for-completion` is set to `true`. Default is `false`.
**Optional.** Set to `'true'` to sync the status of this action with the triggered workflow run. If the triggered workflow run does not succeed, this action will also be set to failed. This only applies if `wait-for-completion` is set to `true`. Default is `false`.

### `success-conclusions`

**Optional.** Comma-separated list of run conclusions that `sync-status` should treat as a pass. Anything else fails this action, as does a run that never reaches `completed`. This only applies if `sync-status` is set to `true`. Default is `success,neutral,skipped,action_required`.

Set it to `success` when the triggered run is a gate and only a genuine pass should go green:

```yaml
- uses: step-security/workflow-dispatch@v1
with:
workflow: e2e.yml
wait-for-completion: 'true'
sync-status: 'true'
success-conclusions: success
```

Valid values are `success`, `failure`, `neutral`, `cancelled`, `skipped`, `timed_out`, `action_required`, `stale` and `startup_failure`. An unrecognised value fails the action before the workflow is dispatched.

## Action Outputs

Expand Down
4 changes: 4 additions & 0 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ inputs:
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
success-conclusions:
description: 'Comma-separated run conclusions that sync-status should treat as a pass. Set to "success" to fail on anything else. Only applies if sync-status is true. Valid values: success, failure, neutral, cancelled, skipped, timed_out, action_required, stale, startup_failure.'
required: false
default: 'success,neutral,skipped,action_required'

outputs:
runId:
Expand Down
34 changes: 32 additions & 2 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40787,11 +40787,32 @@ var version = "1.3.2";

// src/main.ts
var API_VERSION = "2026-03-10";
var DEFAULT_SUCCESS_CONCLUSIONS = "success,neutral,skipped,action_required";
var KNOWN_CONCLUSIONS = /* @__PURE__ */ new Set([
"success",
"failure",
"neutral",
"cancelled",
"skipped",
"timed_out",
"action_required",
"stale",
"startup_failure"
]);
async function run() {
info(`\u{1F3C3} Workflow Dispatch Action v${version}`);
try {
await validateSubscription();
const workflowRef = getInput("workflow");
const successConclusions = new Set(
(getInput("success-conclusions") || DEFAULT_SUCCESS_CONCLUSIONS).split(",").map((c) => c.trim().toLowerCase()).filter((c) => c.length > 0)
);
const unknownConclusions = [...successConclusions].filter((c) => !KNOWN_CONCLUSIONS.has(c));
if (unknownConclusions.length > 0) {
throw new Error(
`Invalid 'success-conclusions' value(s): ${unknownConclusions.join(", ")}. Valid values: ${[...KNOWN_CONCLUSIONS].join(", ")}`
);
}
const token = getInput("token");
const ref = getInput("ref");
const [owner, repo] = getInput("repo") ? getInput("repo").split("/") : [context2.repo.owner, context2.repo.repo];
Expand Down Expand Up @@ -40878,13 +40899,22 @@ Note: The workflow is still running but we have stopped waiting. You can check t
headers: { "x-github-api-version": API_VERSION }
}
);
const runStatusNow = finalRunData.status;
const conclusion = finalRunData.conclusion;
if (conclusion === "failure") {
if (runStatusNow !== "completed") {
setFailed(
`Workflow run did not complete (status: ${runStatusNow}). Check the run details here: ${dispatchResp.data.html_url}`
);
} else if (successConclusions.has(String(conclusion))) {
info(`\u{1F389} Workflow conclusion: ${conclusion}`);
} else 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}`);
setFailed(
`Workflow run concluded '${conclusion}'. Check the run details here: ${dispatchResp.data.html_url}`
);
}
}
} catch (error2) {
Expand Down
48 changes: 45 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ import * as PackageJSON from '../package.json'

const API_VERSION = '2026-03-10' // Latest API version as of March 2026, update as needed

// Conclusions `sync-status` treats as a pass unless `success-conclusions` says
// otherwise. The default keeps runs that deliberately end without doing work
// passing, as they always have.
const DEFAULT_SUCCESS_CONCLUSIONS = 'success,neutral,skipped,action_required'

const KNOWN_CONCLUSIONS = new Set([
'success',
'failure',
'neutral',
'cancelled',
'skipped',
'timed_out',
'action_required',
'stale',
'startup_failure',
])

type Workflow = {
id: number
name: string
Expand All @@ -31,6 +48,21 @@ async function run(): Promise<void> {
// Required inputs
const workflowRef = core.getInput('workflow')

// Read and validate before dispatching: a typo here changes the verdict, so
// failing now beats triggering a run we then refuse to judge.
const successConclusions = new Set(
(core.getInput('success-conclusions') || DEFAULT_SUCCESS_CONCLUSIONS)
.split(',')
.map((c) => c.trim().toLowerCase())
.filter((c) => c.length > 0),
)
const unknownConclusions = [...successConclusions].filter((c) => !KNOWN_CONCLUSIONS.has(c))
if (unknownConclusions.length > 0) {
throw new Error(
`Invalid 'success-conclusions' value(s): ${unknownConclusions.join(', ')}. Valid values: ${[...KNOWN_CONCLUSIONS].join(', ')}`,
)
}

// Optional inputs, with defaults
const token = core.getInput('token')
const ref = core.getInput('ref')
Expand Down Expand Up @@ -154,15 +186,25 @@ async function run(): Promise<void> {
headers: { 'x-github-api-version': API_VERSION },
},
)
const runStatusNow = finalRunData.status
const conclusion = finalRunData.conclusion

// Set this action to failed if the triggered workflow run failed or was cancelled
if (conclusion === 'failure') {
// An incomplete run has no conclusion yet, so passing here would report
// success for work still in flight (e.g. after the wait above timed out).
if (runStatusNow !== 'completed') {
core.setFailed(
`Workflow run did not complete (status: ${runStatusNow}). Check the run details here: ${dispatchResp.data.html_url}`,
)
} else if (successConclusions.has(String(conclusion))) {
core.info(`🎉 Workflow conclusion: ${conclusion}`)
} else 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}`)
core.setFailed(
`Workflow run concluded '${conclusion}'. Check the run details here: ${dispatchResp.data.html_url}`,
)
}
}
} catch (error) {
Expand Down