affaan-m/ECC · error · ValueError

missing output directory

Error message

missing output directory: {part}

What it means

While walking the output tree with create=False, _open_dir expects every intermediate directory to already exist. If os.stat (no-follow, dir_fd-relative) reports FileNotFoundError for a component, the walk converts it into this ValueError naming the missing component instead of silently creating directories the caller did not ask for. Use create=True (as prepare does) when the tree should be built.

Solutions

  1. Call safe.prepare(parts) (create=True) for the directory prefix before writing files into it
  2. Check the path spelling/case of the intermediate directory
  3. Re-run the earlier workflow stage that creates the directory
  4. If the dir should exist, use _open_dir/create mode via prepare rather than write_json directly

Example fix

# before
safe.write_json(('runs', 'run1', 'summary.json'), data)  # runs/run1 missing

# after
safe.prepare(('runs', 'run1'))
safe.write_json(('runs', 'run1', 'summary.json'), data)
Defensive patterns

Strategy: validation

Validate before calling

def ensure_dirs_prepared(safe, parts):
    safe.prepare(tuple(parts[:-1]))  # create intermediate dirs before writing

Type guard

def exists_under_root(safe, parts) -> bool:
    try:
        safe._open_dir(tuple(parts), create=False)
        return True
    except ValueError:
        return False

Try / catch

try:
    safe.write_json(parts, data)
except ValueError as e:
    if e.args[0].startswith('missing output directory'):
        safe.prepare(tuple(parts[:-1]))
        safe.write_json(parts, data)
    else:
        raise

Prevention

When it happens

Trigger: Calling write_json or artifact_metadata with a subdirectory path whose intermediate directory was never created via prepare; referencing a component that was deleted after preparation; a typo in the directory component.

Common situations: Writing into outputs/<run>/styles before calling prepare for that subdirectory; cleaning the output tree between steps; case-sensitivity mismatch (Styles vs styles) on Linux.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/7b5c28572fbf0177. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/workflow.py:207

    def close(self) -> None:
        if self._root_fd >= 0:
            os.close(self._root_fd)
            self._root_fd = -1

    def __del__(self) -> None:
        self.close()

    def _open_dir(self, parts: tuple[str, ...], *, create: bool) -> int:
        current = os.dup(self._root_fd)
        try:
            for part in parts:
                if not part or part in {".", ".."} or "/" in part:
                    raise ValueError("output path contains an invalid component")
                try:
                    metadata = os.stat(part, dir_fd=current, follow_symlinks=False)
                except FileNotFoundError:
                    if not create:
                        raise ValueError(f"missing output directory: {part}") from None
                    os.mkdir(part, mode=0o700, dir_fd=current)
                    metadata = os.stat(part, dir_fd=current, follow_symlinks=False)
                if stat.S_ISLNK(metadata.st_mode):
                    raise ValueError(f"output directory must not be a symlink: {part}")
                if not stat.S_ISDIR(metadata.st_mode):
                    raise ValueError(f"output intermediate must be a directory: {part}")
                child = os.open(
                    part,
                    os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW,
                    dir_fd=current,
                )
                os.close(current)
                current = child
            return current
        except Exception:
            os.close(current)
            raise

View on GitHub (pinned to 8321021c54)