github/spec-kit · error · SystemExit

ERROR: Feature directory not found. Set…

Error message

ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY or ensure .specify/feature.json contains feature_directory.

What it means

When resuming a workflow run, _resolve_resume_root() rejects a persisted installed_registry_root that is absolute, symlink-free, but no longer an existing directory. The run state lives in the installing project; a vanished root cannot be safely rediscovered, and falling back to the current project would read state from an unrelated tree, so it fails closed with ValueError.

Solutions

  1. Reinstall or restore the project that owns the workflow (the path in installed_registry_root must exist again), then resume
  2. Re-run the workflow from scratch in the current project with `workflow run` instead of resuming cross-project state
  3. If the root moved, update the persisted run state's installed_registry_root to the new absolute, non-symlinked location
  4. In ephemeral environments, avoid resuming state persisted on a previous container — always run fresh

Example fix

# before
resume(installed_registry_root='/tmp/ci-build-42/.specify')  # deleted container dir

# after
# fresh run in the current, existing project root
workflow_run(project_root=Path.cwd(), resume=False)
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def can_resume(installed_registry_root: str | None) -> bool:
    if not installed_registry_root:
        return True
    p = Path(installed_registry_root)
    return p.is_absolute() and p.is_dir() and not any(
        (p.joinpath(*p.parts[i+1:])).is_symlink() for i in range(len(p.parts) - 1)
    )

assert can_resume(state.installed_registry_root), 'owner project missing; run fresh instead'

Try / catch

try:
    resume_workflow(run_id, project_root=Path.cwd())
except ValueError as e:
    if 'workflow owner is unavailable' in str(e):
        workflow_run_fresh()  # start a new run in the current project
    else:
        raise

Prevention

When it happens

Trigger: `workflow resume` (or API equivalents reading installed_registry_root) where the recorded registry root was deleted, the checkout was moved, the volume was unmounted, or the path was recorded on another machine/container that no longer exists.

Common situations: Deleting or moving the project that installed the workflow; ephemeral CI containers where /home/... roots vanish between runs; unmounting network volumes; cloning a run-state file between machines; switching between Docker images with different layouts.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/d21a2c98bcd6c4c4. Report an issue: GitHub.

Appendix: source

Thrown at scripts/python/common.py:157

    repo_root = get_repo_root(script_file)
    current_branch = get_current_branch()

    feature_dir_raw = os.environ.get("SPECIFY_FEATURE_DIRECTORY", "")
    if feature_dir_raw:
        feature_dir = Path(feature_dir_raw)
        if not feature_dir.is_absolute():
            feature_dir = repo_root / feature_dir
        if not no_persist:
            persist_feature_json(repo_root, feature_dir_raw)
    elif (repo_root / ".specify" / "feature.json").is_file():
        stored = read_feature_json_feature_directory(repo_root)
        if not stored:
            print(
                "ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY "
                "or ensure .specify/feature.json contains feature_directory.",
                file=sys.stderr,
            )
            raise SystemExit(1)
        feature_dir = Path(stored)
        if not feature_dir.is_absolute():
            feature_dir = repo_root / feature_dir
    else:
        print(
            "ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRECTORY "
            "or run the specify command to create .specify/feature.json.",
            file=sys.stderr,
        )
        raise SystemExit(1)

    if not current_branch:
        current_branch = Path(_trim_trailing_separators(feature_dir)).name

    return FeaturePaths(
        repo_root=repo_root,
        current_branch=current_branch,
        feature_dir=feature_dir,

View on GitHub (pinned to bf88c9f9a8)