Skip to content
7 changes: 6 additions & 1 deletion src/specify_cli/workflows/command_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ def workflow_status(
f"{cli._escape_markup(scope['workflow_id'])}: {scope['status']}"
)
else:
runs = engine.list_runs()
err = cli._error_console(json_output)
try:
runs = engine.list_runs()
except (OSError, ValueError) as exc:
err.print(f"[red]Error:[/red] {cli._escape_markup(str(exc))}")
Comment on lines +103 to +107

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Added workflow status --json regressions for symlinked run directories and state.json, asserting clean failure and stderr-only output. Fixed in 79f1da7.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The workflow status --json listing regressions for symlinked run directories and state.json are present and pass on the current branch (02159bf0).

raise cli.typer.Exit(1)

if json_output:
payload = {
Expand Down
366 changes: 305 additions & 61 deletions src/specify_cli/workflows/engine.py

Large diffs are not rendered by default.

37 changes: 36 additions & 1 deletion tests/specify_cli/workflows/test_command_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from __future__ import annotations

import json
import os
from pathlib import Path

import pytest
import typer



class TestWorkflowJsonOutput:
"""Test the --json machine-readable output for run/resume/status."""

Expand Down Expand Up @@ -95,6 +95,41 @@ def test_status_json_single_and_list(self, project_dir):
)
assert any(r["run_id"] == rid for r in listing["runs"])

@pytest.mark.skipif(not hasattr(os, "symlink"), reason="symlinks are unavailable")
@pytest.mark.parametrize(
"symlink_target", ["run-directory", "state.json", "dangling-runs-root"]
)
def test_status_json_list_rejects_symlinked_storage(
self, project_dir, tmp_path, symlink_target
):
runs_dir = project_dir / ".specify" / "workflows" / "runs"
runs_dir.mkdir(parents=True)
outside = tmp_path / "outside"
outside.mkdir()
(outside / "state.json").write_text(
json.dumps({
"run_id": "run-1",
"workflow_id": "workflow",
"status": "paused",
}),
encoding="utf-8",
)
if symlink_target == "run-directory":
(runs_dir / "run-1").symlink_to(outside, target_is_directory=True)
elif symlink_target == "state.json":
run_dir = runs_dir / "run-1"
run_dir.mkdir()
(run_dir / "state.json").symlink_to(outside / "state.json")
else:
runs_dir.rmdir()
runs_dir.symlink_to(tmp_path / "missing", target_is_directory=True)

result = self._invoke(project_dir, ["workflow", "status", "--json"])

assert result.exit_code == 1
assert result.stdout == ""
assert "symlinked" in result.stderr

def test_composed_gate_status_and_resume(self, project_dir):
from specify_cli.workflows.catalog import WorkflowRegistry

Expand Down
Loading
Loading