Skip to content

feat: add success-conclusions to control what sync-status accepts - #306

Draft
alecsammon wants to merge 2 commits into
step-security:mainfrom
alecsammon:fix_sync_status_strict_mode
Draft

feat: add success-conclusions to control what sync-status accepts#306
alecsammon wants to merge 2 commits into
step-security:mainfrom
alecsammon:fix_sync_status_strict_mode

Conversation

@alecsammon

Copy link
Copy Markdown

Stacked on #304. This branch contains #304's commit plus one more, so the diff below includes it.
Once #304 merges, this PR's diff reduces to just the new input. Happy to rebase or squash it into #304
instead if you would rather review one change.

Why

#304 fixes sync-status reporting success for runs that did not succeed, but it has to pick a single
answer to a question that does not have one: which conclusions count as a pass?

neutral, skipped and action_required are genuinely ambiguous. If the triggered run is a gate —
a required status check, or a deploy waiting on a rollout — then only success should go green, and a
skipped run passing is exactly the kind of silent hole #304 is closing. If instead the caller triggers
optional work, a skipped run passing is correct and failing on it would be a regression. #304 keeps
those three passing to stay non-breaking, which is the safe default but leaves the strict case
unavailable.

This makes it the caller's choice.

What

A success-conclusions input: a comma-separated list of conclusions sync-status treats as a pass.
Anything else fails, as does a run that never reaches completed.

- uses: step-security/workflow-dispatch@v1
  with:
    workflow: e2e.yml
    wait-for-completion: 'true'
    sync-status: 'true'
    success-conclusions: success   # gate: nothing but a genuine pass goes green

The default is success,neutral,skipped,action_required — precisely the set that passes after #304 — so
behaviour is unchanged unless the input is set. That is asserted by the tests below rather than
claimed.

It also replaces #304's hard-coded fail-set with the allowlist, which is a net simplification: one
concept instead of two, and no list of conclusions to keep in sync with the API.

The value is validated against the conclusions the REST API documents, and an unrecognised entry throws
before the workflow is dispatched. A typo like succes would otherwise silently drop out of the
allowlist and flip the verdict with no signal — and failing after dispatch would leave a triggered run
nobody is waiting on, which matters when the dispatched workflow is a deploy.

Verification

The repository has no test suite, so as with #304:

  1. npm run lint (eslint + prettier) passes.
  2. npm run build reproduces the bundle with no unrelated churn.
  3. action.yaml parses.
  4. I table-tested the parsing and the verdict across 20 cases:
--- default input (must equal #304 behaviour) ---
ok  completed/success          -> pass      ok  completed/neutral         -> pass
ok  completed/failure          -> FAIL      ok  completed/skipped         -> pass
ok  completed/cancelled        -> FAIL      ok  completed/action_required -> pass
ok  completed/timed_out        -> FAIL      ok  pending/null              -> FAIL
ok  completed/startup_failure  -> FAIL      ok  in_progress/null          -> FAIL
ok  completed/stale            -> FAIL

--- success-conclusions: success (strict) ---
ok  completed/success          -> pass      ok  completed/action_required -> FAIL
ok  completed/neutral          -> FAIL      ok  completed/timed_out       -> FAIL
ok  completed/skipped          -> FAIL      ok  pending/null              -> FAIL

--- input parsing ---
ok  " success , NEUTRAL " normalises -> success+neutral
ok  typo "succes" throws             -> yes
ok  trailing commas tolerated        -> no throw

Docs updated in README.md and the input described in action.yaml.

alecsammon and others added 2 commits September 10, 2026 11:51
sync-status only fails on a `failure` or `cancelled` conclusion, so every other
non-success outcome sets the step to success:

- `timed_out` — the triggered run hit a job timeout
- `startup_failure` — the run never started (e.g. an invalid workflow file)
- `stale` — the run was discarded before it could report
- a `null` conclusion, which is what the API returns whenever the run has not
  completed, including after `wait-for-completion` gives up at its own timeout

The last case is the easiest to hit: the wait loop exits on its timeout, warns,
and then sync-status reads a `null` conclusion and passes the step — so a job
that waited the full timeout reports success while the run it triggered is
still going.

Fail on the conclusions that unambiguously mean the run did not succeed, and on
any run that has not reached `completed`. `neutral`, `skipped` and
`action_required` keep passing as before, so runs that deliberately end without
doing work are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Which conclusions should count as a pass depends on why the run was triggered.
A gate wants only `success`; a caller that triggers optional work may be happy
for a `skipped` run to pass. Hard-coding either choice is wrong for the other.

`success-conclusions` takes a comma-separated list of conclusions to treat as a
pass, defaulting to the conclusions that pass today, so behaviour is unchanged
unless it is set. Setting it to `success` makes the action green only on a
genuine pass.

The value is validated against the conclusions the API can return, and an
unrecognised entry fails before the workflow is dispatched — a typo would
otherwise silently widen or narrow what counts as a pass, changing the verdict
with no signal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant