Skip to content

feat(workflows): add JSON output for workflow run resume and status#2814

Open
doquanghuy wants to merge 2 commits into
github:mainfrom
doquanghuy:feat/2811-workflow-json
Open

feat(workflows): add JSON output for workflow run resume and status#2814
doquanghuy wants to merge 2 commits into
github:mainfrom
doquanghuy:feat/2811-workflow-json

Conversation

@doquanghuy
Copy link
Copy Markdown
Contributor

@doquanghuy doquanghuy commented Jun 2, 2026

Description

Closes #2811.

Adds an opt-in --json flag to workflow run, workflow resume, and workflow status, giving automation a machine-readable view of workflow terminal states (completed / paused / failed / aborted) and the run id, instead of scraping human prose or reading internal state.json files.

// specify workflow run wf.yml --json
{ "run_id": "662bf791", "workflow_id": "smoke", "status": "paused",
  "current_step_id": "ask", "current_step_index": 0 }

status --json additionally reports per-step states (single run) or a runs list (no run id).

What changed

  • --json option on the three commands; a shared _workflow_run_payload() builder for run/resume and a shared _emit_workflow_json() emitter.
  • JSON is emitted via plain stdout (not console.print) so Rich markup, highlighting, and line-wrapping can never alter the object — verified by a test asserting the output contains no ANSI/markup and round-trips. (This is intentionally not routed through Rich the way version --features --json is.)
  • When --json is set, the step-progress callback and banners are suppressed so stdout is a single parseable object.
  • Reference docs updated (docs/reference/workflows.md).

Compatibility

  • Default (no --json) output and exit codes are unchanged.
  • Usage/lookup errors (e.g. run not found) remain human-readable on the existing path — the JSON object represents a run outcome, not error reporting. Happy to extend to JSON errors if you'd prefer.

Testing

  • Tested locally with uv run specify --help
  • uv sync --extra test && uv run pytest — 3313 passed, 40 skipped; ruff check src/ clean
  • Manually verified run/status (single + list)/resume --json produce parseable output and that default output is unchanged

Tests in tests/test_workflows.py::TestWorkflowJsonOutput cover completed/paused run JSON, status (single + list), resume JSON, default output staying human (non-JSON), and that --json output carries no markup/ANSI.

AI Disclosure

  • I did not use AI assistance for this contribution
  • I did use AI assistance (describe below)

Used Claude to implement the flag, tests, and docs. Behaviour was verified locally and the diff reviewed before submission.


Per CONTRIBUTING.md (new CLI arguments should be agreed first), #2811 is the discussion issue and this PR is the concrete proposal — happy to adjust the payload shape or hold it. @mnriem a steer on whether this is wanted and on the field set would be appreciated.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in --json flag to specify workflow run, workflow resume, and workflow status so automation can consume machine-readable workflow outcomes (run id + terminal status) without scraping Rich-formatted text.

Changes:

  • Added shared JSON payload/emission helpers and a --json option to the three workflow CLI commands.
  • Added pytest coverage for JSON output shape and for “no Rich markup/ANSI” on the JSON path.
  • Updated workflow reference docs to describe the new flag.
Show a summary per file
File Description
src/specify_cli/__init__.py Implements --json options and emits JSON payloads for run/resume/status.
tests/test_workflows.py Adds CLI-level tests validating JSON output and ensuring it round-trips without ANSI/markup.
docs/reference/workflows.md Documents the new --json flags for run/resume/status.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

src/specify_cli/init.py:4253

  • --json mode currently doesn't ensure stdout remains a single parseable JSON object because workflow steps can still stream output to stdout during execution. In particular, command steps call IntegrationBase.dispatch_command(..., stream=True) by default (streams subprocess stdout/stderr directly to the terminal), and prompt steps run subprocess.run(...) without capturing output—both will interleave human output before the final JSON and break json.loads() for real workflows like workflows/speckit/workflow.yml that use command: steps.
    try:
        state = engine.execute(definition, inputs)
    except ValueError as exc:
        console.print(f"[red]Error:[/red] {exc}")
        raise typer.Exit(1)
  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment thread docs/reference/workflows.md Outdated
Comment on lines +27 to +30
specify workflow run wf.yml --json
# {"run_id": "662bf791", "workflow_id": "wf", "status": "paused",
# "current_step_id": "review", "current_step_index": 0}
```
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed: the docs example is now pretty-printed (indent=2) to match exactly what the CLI emits, with a note that it's plain stdout with no Rich markup.

Adds an opt-in `--json` flag to `workflow run`, `workflow resume`, and
`workflow status` that emits a single machine-readable object (run_id,
workflow_id, status, current step; status also reports per-step states
and a runs list) for automation and external orchestrators.

JSON is written via a small `_emit_workflow_json` helper using plain
stdout, so Rich markup, highlighting, and line-wrapping can never alter
the emitted object. Default human-readable output and exit codes are
unchanged when `--json` is omitted. Reference docs updated.

Closes github#2811.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@doquanghuy doquanghuy force-pushed the feat/2811-workflow-json branch from c734b8e to 94f33e6 Compare June 3, 2026 04:15
@doquanghuy
Copy link
Copy Markdown
Contributor Author

Thanks @copilot — fixed in the latest push.

The --json example in docs/reference/workflows.md now shows the exact pretty-printed output the CLI emits (json.dumps(..., indent=2), real key order) in a ```json block, instead of the wrapped single-line form. Verified the documented object parses and matches the emitter's field order.

@mnriem ready for review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment thread src/specify_cli/__init__.py Outdated
Comment on lines +4349 to +4350
"current_step_id": state.current_step_id,
"steps": {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed: the single-run status --json payload is now built on the shared _workflow_run_payload(state) ({**_workflow_run_payload(state), created_at, updated_at, steps}), so current_step_index — and every other common field — is identical across run/resume/status. A test asserts that equivalence so it can't drift again.

Comment thread docs/reference/workflows.md Outdated
Comment on lines +33 to +36
"workflow_id": "wf",
"status": "paused",
"current_step_id": "review",
"current_step_index": 0
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed: the example now uses my-pipeline.yml with "workflow_id": "build-and-review" and a one-line note that workflow_id is the YAML workflow.id, not the file name.

…json

# Conflicts:
#	docs/reference/workflows.md
#	src/specify_cli/__init__.py
#	tests/test_workflows.py
@doquanghuy
Copy link
Copy Markdown
Contributor Author

Pushed an update that brings the branch up to date with main and addresses Copilot's review.

Up to date with main. Resolved the overlap with the new resume --input work (#2815): workflow resume now carries both --input and --json, and run/resume share main's _parse_input_values helper. Full uv run pytest is green (3472 passed), ruff check src/ and markdownlint clean.

Copilot's notes.

  • status <run_id> --json omitted current_step_index. The single-run status payload is now built on the shared _workflow_run_payload(state) ({**_workflow_run_payload(state), created_at, updated_at, steps}), so the common step-position fields are byte-identical across run/resume/status and can't drift again. Added an assertion locking that equivalence.
  • Docs example output format. The example is pretty-printed (indent=2) to match exactly what's emitted, with a note that it's plain stdout, no Rich markup.
  • wf.yml"workflow_id": "wf" could read as filename-derived. Switched the example to my-pipeline.yml with "workflow_id": "build-and-review" and a one-line note that workflow_id is the YAML workflow.id, not the file name.

The payload shape is otherwise unchanged from the original proposal. @mnriem a steer on whether this is wanted, and on the field set, would be appreciated.

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.

[Feature]: Machine-readable output for workflow run, resume, and status

3 participants