Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
bba88bb
feat(workflows): align workflow CLI with extension command surface
marcelsafin Jul 8, 2026
cc8143e
fix(workflows): preserve disabled state on update, guard corrupted re…
marcelsafin Jul 8, 2026
59a791b
fix(workflows): guard list against corrupted registry entries, re-rai…
marcelsafin Jul 8, 2026
52ab7ff
fix(workflows): escape rich markup in id-mismatch errors and validate…
marcelsafin Jul 9, 2026
4c356c4
fix(workflows): escape rich markup in list output and catalog install…
marcelsafin Jul 9, 2026
8409bca
fix(workflows): escape rich markup in --from download exception message
marcelsafin Jul 9, 2026
129362f
fix(workflows): catch OSError in per-workflow update loop and make re…
marcelsafin Jul 10, 2026
4ca50f5
fix(workflows): escape rich markup in search output
marcelsafin Jul 10, 2026
10f9770
fix: escape workflow validation errors before Rich output
marcelsafin Jul 10, 2026
91c7165
fix(workflows): escape remaining unescaped Rich markup paths
marcelsafin Jul 10, 2026
7100211
fix(workflows): escape workflow name/id in install success messages
marcelsafin Jul 10, 2026
a530725
fix(workflows): fail cleanly on unparseable catalog install URLs
marcelsafin Jul 10, 2026
560c7cd
fix(workflows): reject catalog updates whose downloaded version misma…
marcelsafin Jul 10, 2026
e44d214
fix(workflows): validate workflow ID in run command and document new …
marcelsafin Jul 10, 2026
8efa3c0
fix(workflows): enforce disabled state for direct paths to installed …
marcelsafin Jul 10, 2026
96d3b38
fix(workflows): reject explicit empty --from URL instead of catalog f…
marcelsafin Jul 10, 2026
f28e125
fix(workflows): registry rollback on save failure, consistent disable…
marcelsafin Jul 10, 2026
338f7ee
fix(workflows): guard non-string catalog URL and keep enable/disable …
marcelsafin Jul 10, 2026
82ecd05
fix(workflows): tolerate non-dict registry entries in add and clarify…
marcelsafin Jul 10, 2026
6b25c75
fix(workflows): atomic registry save and accurate mixed-target update…
marcelsafin Jul 10, 2026
eabce4e
fix(workflows): exclusive temp file for registry save and cwd-indepen…
marcelsafin Jul 10, 2026
cac5300
fix(workflows): symlink guards and shape validation in workflow regis…
marcelsafin Jul 10, 2026
a69bb18
fix(workflows): validate download redirects before following them
marcelsafin Jul 10, 2026
6a0c462
test(workflows): accept redirect_validator kwarg in step-add open_url…
marcelsafin Jul 10, 2026
3763e6f
fix(workflows): guard directory-shaped workflow.yml and unreadable re…
marcelsafin Jul 10, 2026
86de599
fix(workflow): rollback registry.remove() and guard OSError at CLI bo…
marcelsafin Jul 11, 2026
fb64ddd
fix(workflow): preserve prior catalog install on reinstall registry-s…
marcelsafin Jul 11, 2026
49f6fb6
fix(workflow): centralize catalog-install cleanup across all failure …
marcelsafin Jul 11, 2026
812050a
fix(workflow): restore registry entry verbatim on post-removal rmtree…
marcelsafin Jul 11, 2026
6a127b3
Fix 4 current Copilot review findings on workflow run/registry/install
marcelsafin Jul 11, 2026
12d985b
Fix disabled-workflow bypass via symlinked .specify project root
marcelsafin Jul 11, 2026
ce3e0ab
Fix raw exception leak in bundle remove primitive boundary
marcelsafin Jul 11, 2026
6b593e3
Fix 8 current Copilot review findings on registry fail-closed, rollba…
marcelsafin Jul 11, 2026
db45f6c
Fix 3 current Copilot review findings: bookkeeping-aware BundlerError…
marcelsafin Jul 11, 2026
b8269c8
Fix temp-file leak in workflow add --from and strengthen size-limit t…
marcelsafin Jul 11, 2026
00465c3
Harden workflow install/remove transactions with atomic staging
marcelsafin Jul 11, 2026
7c6bc90
Discard reinstall backup file after registry.add() succeeds
marcelsafin Jul 11, 2026
915caf8
Clean up freshly-created dest_dir when staging mkstemp fails
marcelsafin Jul 11, 2026
792450e
Fix installed-workflow ownership/disabled bypass and resume enforcement
marcelsafin Jul 11, 2026
800b962
Guard --from temp cleanup; drop redundant update rollback; mark POSIX…
marcelsafin Jul 11, 2026
616f727
Report possible partial changes on zero-removed bundle removal failure
marcelsafin Jul 11, 2026
4f24735
Fix workflow resume disabled-check bypass after project move/rename
marcelsafin Jul 11, 2026
fa410d3
Fix silenced cleanup failures and malformed run-state type validation
marcelsafin Jul 11, 2026
8e69966
Add ValueError boundary to workflow status single-run lookup
marcelsafin Jul 11, 2026
a837fb5
fix: bound workflow step downloads
marcelsafin Jul 11, 2026
db67e96
fix: preserve workflow reinstall state
marcelsafin Jul 11, 2026
a874b97
fix: fail closed on workflow registry state
marcelsafin Jul 11, 2026
c098d86
fix: make workflow installs transactional
marcelsafin Jul 11, 2026
e799957
fix: close workflow transaction races
marcelsafin Jul 11, 2026
d964c97
fix: clean up failed workflow transactions
marcelsafin Jul 11, 2026
6b968ef
fix: clean up workflow removal state
marcelsafin Jul 11, 2026
c82e214
fix: guard workflow update transactions
marcelsafin Jul 11, 2026
26551ae
fix: harden workflow lifecycle edge cases
marcelsafin Jul 11, 2026
ec53251
fix: verify installed workflow ownership
marcelsafin Jul 11, 2026
77116a1
fix: isolate workflow rollback cleanup
marcelsafin Jul 11, 2026
8f795a6
fix: preserve unique workflow backups
marcelsafin Jul 11, 2026
da86926
fix: bind workflow staging to file descriptors
marcelsafin Jul 11, 2026
2c47c86
fix: fail closed on corrupt workflow registry
marcelsafin Jul 11, 2026
5163aa2
fix: bind workflow ownership and source identity
marcelsafin Jul 11, 2026
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
29 changes: 26 additions & 3 deletions docs/reference/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,30 @@ Lists workflows installed in the current project.
specify workflow add <source>
```

| Option | Description |
| --------------- | ------------------------------------------------------ |
| `--dev` | Install from a local workflow YAML file or directory |
| `--from <url>` | Install from a custom URL (`<source>` names the expected workflow ID) |

Installs a workflow from the catalog, a URL (HTTPS required), or a local file path.

## Update Workflows

```bash
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

```bash
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

```bash
Expand All @@ -102,9 +124,10 @@ Removes an installed workflow from the project.
specify workflow search [query]
```

| Option | Description |
| ------- | --------------- |
| `--tag` | Filter by tag |
| Option | Description |
| ---------- | ----------------- |
| `--tag` | Filter by tag |
| `--author` | Filter by author |

Searches all active catalogs for workflows matching the query.

Expand Down
42 changes: 32 additions & 10 deletions src/specify_cli/bundler/services/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,19 +187,41 @@ def remove_bundle(

still_needed = components_still_needed(records, exclude_bundle_id=bundle_id)
result = InstallResult(bundle_id=bundle_id)
remove_attempted = False

for component in target.contributed_components:
key = (component.kind, component.id)
if key in still_needed:
result.skipped.append(component)
continue
if installer.is_installed(project_root, component):
installer.remove(project_root, component)
result.uninstalled.append(component)
try:
for component in target.contributed_components:
key = (component.kind, component.id)
if key in still_needed:
result.skipped.append(component)
continue
if installer.is_installed(project_root, component):
remove_attempted = True
installer.remove(project_root, component)
result.uninstalled.append(component)
save_records(project_root, remove_record(records, bundle_id))
except Exception as exc: # noqa: BLE001
Comment thread
marcelsafin marked this conversation as resolved.
if result.uninstalled:
detail = (
f"{len(result.uninstalled)} component(s) were already removed "
"before this failure; the bundle record was left unchanged, "
"so the project may be partially uninstalled."
)
elif remove_attempted:
detail = (
"No components were removed, but the failing component may "
"have made partial changes before raising, so the project "
"may be partially uninstalled."
)
else:
result.skipped.append(component)
detail = (
"No components were removed and no removal was attempted; "
"the bundle record was left unchanged."
)
raise BundlerError(
f"Failed to remove bundle '{bundle_id}': {exc}. {detail}"
) from exc
Comment thread
marcelsafin marked this conversation as resolved.

save_records(project_root, remove_record(records, bundle_id))
return result


Expand Down
Loading