github/spec-kit · error · SystemExit
ERROR: Feature directory not found. Set SPECIFY_FEATURE_DIRE
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.
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)
Solutions
- Reinstall or restore the project that owns the workflow (the path in installed_registry_root must exist again), then resume
- Re-run the workflow from scratch in the current project with `workflow run` instead of resuming cross-project state
- If the root moved, update the persisted run state's installed_registry_root to the new absolute, non-symlinked location
- 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
- Don't move or delete the project that installed a workflow while runs are resumable
- In ephemeral CI, use fresh runs instead of resuming state from prior containers
- If a root moved intentionally, update the persisted installed_registry_root to the new path
- Mount persistent volumes at stable absolute paths if cross-run resume is required
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
- Error: branch_template must include the {number} token so ge
- Invalid extension registry {registry}: root must be a mappin
- Invalid extension registry {registry}: 'extensions' must be
- specify event ${{command}} (${{event}}) failed: ${{(e as Err
- errors.join('; ')
AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14).
Data as JSON: /api/errors/d21a2c98bcd6c4c4.
Report an issue: GitHub.