Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
docs(workflows): use qualified StepStatus.* instead of bare FAILED/CO…
…MPLETED/PAUSED

Three Copilot inline comments on workflows/README.md lines 226, 282, 288
flagged that ``StepResult(status=FAILED, ...)`` is not valid Python —
``StepResult.status`` is a ``StepStatus`` enum value, so the
documented form should be ``StepStatus.FAILED``.

Audited the whole PR diff for the same shorthand. The bare unqualified
form appears in three files added/modified by this PR:

1. workflows/README.md (6 sites) — three ``StepResult(status=FAILED, ...)``
   parentheticals, plus the on_reject Notes bullet listing the three
   step statuses (``FAILED``, ``COMPLETED``, ``PAUSED``).

2. tests/test_workflows.py (4 sites) — section header for
   TestContinueOnError, two test-method docstrings, one inline comment
   about a gate's TTY-fallback behaviour.

3. src/specify_cli/workflows/engine.py (1 site) — the validator inline
   comment added in d0b9e00 said "returns FAILED" where the engine
   code itself uses ``StepStatus.FAILED``.

All 11 sites normalised to the qualified ``StepStatus.<name>`` form so
the docs / test docstrings / inline comments match what readers will
actually find in the engine code and the tests. Engine semantics,
validation, and test bodies are unchanged.

161/161 tests/test_workflows.py pass locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  • Loading branch information
doquanghuy and claude committed Jun 1, 2026
commit d82f02113b2f882756d4046f1bbc2c32822e8a7b
2 changes: 1 addition & 1 deletion src/specify_cli/workflows/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ def _validate_steps(
errors.extend(step_errors)

# Validate optional `continue_on_error` field. The engine honours
# this on any step that returns FAILED so the pipeline can route
# this on any step that returns StepStatus.FAILED so the pipeline can route
# around the failure via a downstream `if` or `switch` (or a
# `gate` that surfaces the failure to the operator via message
# interpolation). The field must be a literal boolean —
Expand Down
8 changes: 4 additions & 4 deletions tests/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -2336,7 +2336,7 @@ def test_workflow_without_context_reference_unchanged(self, project_dir):
# ===== continue_on_error Tests =====
#
# Locks the contract documented in workflows/README.md "Error Handling"
# section: when a step returns `StepResult(status=FAILED, ...)` and
# section: when a step returns `StepResult(status=StepStatus.FAILED, ...)` and
# `continue_on_error: true` is declared, the engine records the step's
# `output` (with `exit_code` and `stderr` from the failure) and its
# `status` (sibling key on `steps.<id>`, not nested under `output`)
Expand All @@ -2352,7 +2352,7 @@ class TestContinueOnError:

def test_undeclared_failure_halts_run(self, project_dir):
"""Default behaviour (no `continue_on_error`): a failing step
halts the workflow run with `status == FAILED`.
halts the workflow run with `status == StepStatus.FAILED`.

Locks the byte-equivalent default — workflows that do not
declare the flag must behave exactly as before this feature.
Expand Down Expand Up @@ -2419,7 +2419,7 @@ def test_declared_and_fired_continues_run(self, project_dir):

def test_declared_but_step_succeeded_is_noop(self, project_dir):
"""`continue_on_error: true` on a step that succeeds is a
no-op — the flag only changes behaviour on FAILED status.
no-op — the flag only changes behaviour on StepStatus.FAILED status.
"""
from specify_cli.workflows.engine import WorkflowDefinition, WorkflowEngine
from specify_cli.workflows.base import RunStatus
Expand Down Expand Up @@ -2505,7 +2505,7 @@ def test_gate_abort_still_halts_with_continue_on_error(

# Force the gate step into interactive mode and feed a "reject"
# choice so the abort path actually runs in the test env
# (default behaviour returns PAUSED when stdin is not a TTY).
# (default behaviour returns StepStatus.PAUSED when stdin is not a TTY).
# Swap sys.stdin itself for a stub: setattr on the real
# TextIOWrapper's `isatty` method is not assignable under some
# runners (e.g. pytest with capture disabled).
Expand Down
12 changes: 6 additions & 6 deletions workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Aggregate results from fan-out steps:

## Error Handling

By default, any step that returns `StepResult(status=FAILED, ...)`
By default, any step that returns `StepResult(status=StepStatus.FAILED, ...)`
at runtime halts the entire run — most commonly a `shell` or
`command` step exiting non-zero. Set `continue_on_error: true` on
a step to record its result and continue to the next sibling step
Expand Down Expand Up @@ -265,10 +265,10 @@ A few things worth knowing about that example:
workflow author's responsibility: read
`{{ steps.<gate-id>.output.choice }}` in a follow-up `if`, `switch`,
or expression, as the `recover` step above does.
- `on_reject` has three values: `abort` (default — reject → `FAILED`
- `on_reject` has three values: `abort` (default — reject → `StepStatus.FAILED`
with `output.aborted = True`, halts the run), `skip` (reject →
`COMPLETED`, author handles branching as shown), and `retry`
(reject → `PAUSED` so the next `specify workflow resume` re-runs
`StepStatus.COMPLETED`, author handles branching as shown), and `retry`
(reject → `StepStatus.PAUSED` so the next `specify workflow resume` re-runs
the gate).
- Gates do not automatically re-run the failed step. To express a
retry path, either define custom gate options and branch on the
Expand All @@ -279,12 +279,12 @@ A few things worth knowing about that example:
- The field must be a literal boolean (`true` / `false`); coerced
strings like `"true"` are rejected at validation time.
- **Scope: returned failures only.** The flag applies to step results
with `status=FAILED`. Unhandled exceptions raised out of a step's
with `status=StepStatus.FAILED`. Unhandled exceptions raised out of a step's
`execute()` method are caught one level up by `WorkflowEngine.execute()`,
logged as `workflow_failed`, and abort the run regardless of
`continue_on_error`. If a step author wants the flag to cover an
exceptional path, the step must catch the exception internally and
return `StepResult(status=FAILED, ...)` with the failure encoded in
return `StepResult(status=StepStatus.FAILED, ...)` with the failure encoded in
`output` (e.g. `exit_code`, `stderr`, or a custom field).
Comment thread
mnriem marked this conversation as resolved.
- Gate aborts (`on_reject: abort` chosen by the operator) always halt
the run — `continue_on_error` does not override them. The flag is
Expand Down
Loading