Workflow CLI reference
The specify workflow group manages workflow packages and run state. Most commands require an initialized Spec Kit project; local YAML runs are the exception. Options are command-specific: --json is not a group-wide flag.
Run a Workflow
specify workflow run <source>
| Option | Description |
|---|---|
-i / --input |
Pass input values as key=value (repeatable) |
--json |
Emit the run outcome as a single JSON object |
Runs an installed, enabled workflow ID or a local YAML file. Install a catalog
workflow or URL with specify workflow add before running it by ID. Inputs
declared by the workflow can be provided via --input or prompted interactively.
Example:
specify workflow run speckit -i spec="Build a kanban board with drag-and-drop task management"
Input values follow the input type contracts.
With --json, a single machine-readable object is printed instead of formatted text (the default output is unchanged when the flag is omitted):
specify workflow run my-pipeline.yml --json
{
"run_id": "662bf791",
"workflow_id": "build-and-review",
"status": "paused",
"current_step_id": "review",
"current_step_index": 0
}
workflow_id is the workflow.id declared inside the YAML, not the file name. The object is printed exactly as shown — pretty-printed with two-space indentation, on plain stdout with no Rich markup — so it always parses. While the workflow runs under --json, any progress a step would print (for example a gate prompt, or output from a prompt step's CLI subprocess) is redirected to stderr, so stdout carries only the JSON object. Read the object from stdout; leave stderr attached to the terminal or capture it separately.
For failed and aborted runs, the payload includes an error field carrying the terminal step's error message:
{
"run_id": "662bf791",
"workflow_id": "build-and-review",
"status": "failed",
"current_step_id": "boom",
"current_step_index": 0,
"error": "Command exited with code 3"
}
completed and paused runs omit the error field. The error is persisted in the run's state.json, so specify workflow status <run_id> --json surfaces the same message after the fact.
Adapter-load failures before creating or loading run state use a pre-run failure envelope: a new run has no run ID, while resume retains the supplied run ID but has no workflow or step context. After run state exists, adapter failures from per-step reloads or verified lazy imports retain the actual run, workflow, and current-step fields, just like execution I/O failures. If saving state fails, the on-disk status may still reflect the last successful save rather than the reported I/O failure.
Note: Most workflow commands require a project already initialized with
specify init. The exception isspecify workflow run <local-file.{yml,yaml}>, which can run outside a project; in that case, run state is stored under the current directory's.specify/workflows/runs/<run_id>/.
Resume a Workflow
specify workflow resume <run_id>
| Option | Description |
|---|---|
-i / --input |
Updated input values as key=value (repeatable) |
--json |
Emit the resume outcome as a single JSON object |
Resumes a paused or failed workflow run from its persisted execution state. A
running run is not resumable. Resume replays completed work into its
expression contexts without running step implementations again, then continues
at the unfinished occurrence. This lets a run continue with information that
only became available after it paused, or with a corrected value after a
failure.
Supplied --input values are merged over the run's stored root inputs and
re-validated against the workflow's input types. Unknown root input names are
ignored. Updated values affect only unfinished work; completed steps and calls
are not reinterpreted or repeated:
specify workflow resume <run_id> --input cmd="exit 0"
Workflow Status
specify workflow status [<run_id>]
| Option | Description |
|---|---|
--json |
Emit run status (or the runs list) as a JSON object |
Shows the status of a specific run, or lists all runs if no ID is given. Run states: created, running, completed, paused, failed, aborted.
Workflow Definition
specify workflow definition <run_id> --json
| Option | Description |
|---|---|
--json |
Required. Emit the run's workflow definition as a JSON object |
Returns the workflow definition a run was started with, so an integration can render a run (for example the gates it declares and their messages) without drifting from what the engine actually executes. It reads only the run's own persisted data and never the installed workflow: editing or removing the installed workflow does not change the output. Runtime progress and the active gate come from specify workflow status --json.
Omitting --json exits with code 2 and prints a usage message on stderr; no stdout is produced. A text renderer is deferred, so the JSON shape below is the only contract.
{
"run_id": "662bf791",
"definition": {
"schema_version": "1.0",
"workflow": { "id": "spec-review", "name": "Spec review", "version": "1.0.0" },
"steps": [
{ "id": "draft", "type": "command", "command": "speckit.specify" },
{ "id": "approve", "type": "gate", "message": "Review the specification." }
]
},
"workflow_scopes": []
}
definitionis the workflow as persisted for the run, verbatim and with overlays already applied, so it reflects the steps as executed. Every key is passed through (includingrequires,outputsand unknown keys). Nothing is added, renamed or filled in: an omitted field, such as a gate'soptions, means the engine default applies at run time. Expressions ({{ ... }}) are not evaluated. The structure follows the workflow YAML schema, soschema_versionis the version of this interface and a schema change is a change to it; becausedefinitionis emitted as authored, an omittedschema_versiondefaults to"1.0"and the engine comparesstr(schema_version)(so unquotedschema_version: 1.0in YAML appears as the JSON number1.0). Keys may be added to the top-level response envelope (run_id,definition,workflow_scopes) in future versions, but existing envelope keys are never removed or renamed.- Plain JSON. YAML-native values that JSON cannot represent, such as an unquoted date, appear as strings. Binary values (
!!binary) are not emitted: each appears as a"<binary: N bytes>"placeholder, and the value itself stays in the run's snapshot. A set (!!set) appears as a list sorted by its JSON form, so the output does not depend on hash randomization. Non-string mapping keys and non-finite numbers are stringified too. Two mapping keys that collapse to the same JSON property name (for example1and"1") make the command fail rather than emit lossy JSON. JSON cannot share a reference, so a reused YAML alias (anchor) is duplicated; a recursive alias, or a non-recursive alias graph that expands past a fixed node or output-size budget, fails instead of consuming unbounded CPU, memory and stdout. The budget applies to each definition separately (the root and every entry inworkflow_scopes), so a run with many bound workflow calls is not rejected for its total size. A separate, much larger limit (32 MiB) caps the whole response, so a loop or fan-out that repeats a large definition fails instead of multiplying it. A non-string mapping key longer than 256 characters once stringified is rejected for the same reason. workflow_scopes(always present) holds the frozen definition of every workflow call that has been reached, in execution order. Each entry is{ "scope_path", "workflow_id", "definition" }, withscope_pathandworkflow_ididentical to the values instatus --json. A call step insidedefinitionstays as authored; the callee is never expanded in place. A call that has not been reached yet, or whose target could not be resolved, has no entry, and its definition is never read from the installed workflow. The list grows as calls are reached, and a call inside a loop or fan-out has one entry per iteration or item.- Not included: dynamically generated steps. The output contains the authored root definition and the frozen definitions of bound workflow calls. Steps that a custom step type generates at run time through
StepResult.next_stepsare persisted in the run's execution state but are not exported, so a gate created that way has no definition in this output. The built-inif,switch,while,do-whileandfan-outsteps take their steps from the authored definition and are covered. scope_pathis a list of strings from the root: the authoredidof every enclosing step (step-<index>for a step without anid). Containers such asif,switch, loops and fan-out are elements; each loop iteration and fan-out item adds its index as a string, including the first (["loop", "0", "call"]).workflow_scopesis always present here, whilestatus --jsonincludes it only when the run has workflow calls.
Matching a gate to its definition
status --json reports an active gate as gate.step_id and gate.scope_path. To find the gate's definition:
- Read
gate.scope_pathas[]when it is missing:status --jsonomits it for a gate directly in the root workflow. Then take theworkflow_scopesentry with the longestscope_paththat is a prefix ofgate.scope_path. If there is none, use the rootdefinition. Do not require an exact match:gate.scope_pathlists every enclosing step, includingif,switch, loops and fan-out containers, while an entry'sscope_pathends at the call step. A gate in anifnamedwrapinside a workflow called bycall, itself in anifnamedroute, reports["route", "call", "wrap"], and its entry has["route", "call"]. - In that definition, find the step whose
idequals the authored step ID. In later loop iterations and in fan-out itemsstep_idis qualified as<container>:<id>:<n>(for examplefan:g:0); the authored ID is the second-to-last:-separated segment, because:is not allowed in authored step IDs. Step IDs are unique within a definition except across fan-out templates, which have their own ID namespace: a root step and a fan-out template step may share an ID. Ifgate.scope_pathruns through a fan-out step, look only inside that step'ssteptemplate; otherwise ignore fan-out templates. For example, a root shellgand a gategin the template of fan-outfanboth exist, andfan:g:0withscope_path["fan", "0"]is the template's gate, not the root shell.
A gate generated at run time by a custom step has no definition to match; see the note on dynamically generated steps above.
status --json does not report per-step status for steps inside called workflows.
Errors
Failures write a single {"error": "<message>"} object to stderr, leave stdout empty and exit with code 1: not a Spec Kit project, an invalid SPECIFY_INIT_DIR, unsafe (including symlinked) workflow storage, an invalid or unknown run ID, an unreadable or too deeply nested run state, a run with no persisted definition (for example one created before definitions were persisted), a corrupt definition or execution state, a definition whose mapping keys collapse to the same JSON property name, and a definition with recursive or too-large-to-expand YAML aliases. Usage errors with --json also use this JSON error contract and exit with code 2.
List Installed Workflows
specify workflow list
Lists workflows installed in the current project.
Install a Workflow
specify workflow add <source>
| Option | Description |
|---|---|
--dev |
Install from a local YAML file, package directory, or archive |
--from <url> |
Install from a custom URL (<source> names the expected workflow ID) |
--version <version> |
Install an exact advertised catalog release (<source> must be a workflow ID) |
Installs a workflow from the catalog, an HTTPS URL, a local YAML file, a
directory containing workflow.yml, or a .zip, .tar.gz, or .tgz
archive. Archives may contain workflow.yml at the root or inside one
top-level directory.
Directory and archive installs preserve the complete workflow package,
including scripts and other companion files. ZIP, .tar.gz, and .tgz
archives follow the same validation and installation behavior.
For exact-version catalog selection and its digest requirements, see Catalog releases.
Update Workflows
specify workflow update [workflow_id]
Updates one installed catalog workflow — or all of them when no ID is given — to the latest catalog version. Prompts for confirmation and keeps the installed copy if a download or validation fails.
Enable or Disable a Workflow
specify workflow enable <workflow_id>
specify workflow disable <workflow_id>
Disabled workflows stay installed and listed (marked [disabled]) but refuse to run until re-enabled.
Remove a Workflow
specify workflow remove <workflow_id>
Removes an installed workflow from the project.
Search Available Workflows
specify workflow search [query]
| Option | Description |
|---|---|
--tag |
Filter by tag |
--author |
Filter by author |
Searches all active catalogs for workflows matching the query.
Workflow Info
specify workflow info <workflow_id>
specify workflow info <workflow_id> --versions
Shows detailed information about a workflow, including its steps, inputs, and requirements.
--versions lists the current catalog version followed by advertised historical
versions and indicates whether the winning catalog is installable or
discovery-only (not installable). It also works when a different version is
installed locally.
Resolve a Workflow
specify workflow resolve <workflow-id>
Shows the base/overlay stack and step source attribution. It does not display complete shell commands or environment bindings; review the contributing source files before execution.
Overlay Management
Add a Project Overlay
specify workflow overlay add <path-to-overlay.yml> --priority <n>
Validates the overlay file and copies it to .specify/workflows/overlays/<extends>/<id>.yml. --priority defaults to 10 and overrides the priority field in the file.
List Overlays
specify workflow overlay list <workflow-id>
Shows all overlays for the workflow, ordered by resolver precedence. Disabled overlays are marked as disabled in the listing and are ignored during workflow resolution.
Change Priority
specify workflow overlay set-priority <workflow-id> <overlay-id> <n>
Enable or Disable
specify workflow overlay disable <workflow-id> <overlay-id>
specify workflow overlay enable <workflow-id> <overlay-id>
Remove
specify workflow overlay remove <workflow-id> <overlay-id>
Removes the project overlay file.
Step Management
Custom steps contain executable Python. Review their source before installation and use. See Developing custom step packages for the package contract.
specify workflow step list
specify workflow step search [query]
specify workflow step info <step_id>
specify workflow step info <step_id> --versions
list shows installed custom step types; search searches active step catalogs; info shows installed or catalog metadata and source provenance. --versions lists releases from the winning catalog.
Install from a local directory
specify workflow step add my-step --dev /path/to/my-step
--dev takes a directory (not an archive, not a bare step.yml) that is a
complete package. This needs no catalog, server, or network, which makes it the
supported local-authoring loop:
specify workflow step add my-step --dev ./my-step
specify workflow step list
specify workflow step info my-step
# edit ./my-step, then replace the installed copy:
specify workflow step add my-step --dev ./my-step --force
specify workflow step remove my-step
Install from an archive URL
specify workflow step add my-step --from https://example.com/my-step.zip
--from accepts a .zip, .tar.gz, or .tgz archive (a bare step.yml
URL is not a package). The archive may place step.yml and __init__.py
at its root or under exactly one top-level directory; unrelated top-level
siblings are rejected. Because a step package contains executable Python, a
direct URL install shows a default-deny trust confirmation before any network
request; declining cancels with no request and no error. HTTPS is required
(HTTP is permitted only for loopback hosts), redirects must remain secure, and
downloads are size-bounded.
Install from the catalog
specify workflow step add my-step
Catalog installs resolve individual file URLs from the active step catalogs and
then go through the same validation and commit path as --dev and --from.
Discovery-only catalogs cannot be installed from.
To submit a custom step type to the discovery-only community catalog, follow Community Workflow Steps. Submissions use tag-pinned individual file URLs and per-file digests; maintainers validate metadata and distribution evidence, not submitted code.
See Step releases for catalog history and exact-version selection.
Replacement and force
specify workflow step add my-step --dev ./my-step --force
specify workflow step add my-step --from https://example.com/my-step.zip --force
--force first stages and validates the replacement before touching the
existing installation, and can replace both a registered install and a leftover
unregistered directory. Validation and staging failures leave the previous
package untouched. If removing the old directory fails, the replacement is not
published. If publishing the replacement or updating the registry fails after
the old directory has been removed, the installation may be left incomplete:
rerun the command with the original source and --force to reinstall. No
automatic rollback is attempted.
Listing, running, and removing
Installed custom steps appear in specify workflow step list and are loaded
automatically by workflow add, workflow run, and workflow resume. Remove
one with:
specify workflow step remove my-step
Catalog Management
Workflow and step catalogs are independent stacks.
List Catalogs
specify workflow catalog list
specify workflow step catalog list
Shows all active catalog sources.
Add a Catalog
specify workflow catalog add <url> [--name <name>]
specify workflow step catalog add <url> [--name <name>]
| Option | Description |
|---|---|
--name <name> |
Optional name for the catalog |
Adds a custom catalog URL to the project's .specify/workflow-catalogs.yml
or, for step types, .specify/step-catalogs.yml. New entries allow installation
and receive a priority after existing entries. Only add vetted sources.
Configure discovery-only sources by editing the catalog configuration.
Re-adding the same workflow or step catalog URL with the same name succeeds without changing the configuration; a different name is rejected.
Remove a Catalog
specify workflow catalog remove <index>
specify workflow step catalog remove <index>
Removes a project catalog by its index in the corresponding priority-ordered catalog list.
Sources supplied by SPECKIT_WORKFLOW_CATALOG_URL or SPECKIT_STEP_CATALOG_URL cannot be removed this way; unset the variable to manage project sources.