affaan-m/ECC · error · RuntimeError
secure output requires O_NOFOLLOW and O_DIRECTORY
Error message
secure output requires O_NOFOLLOW and O_DIRECTORY
What it means
_SafeOutput implements a secure output tree using dir_fd-relative, no-follow file operations, which require the platform's os.O_NOFOLLOW and os.O_DIRECTORY flags. If either attribute is missing (unsupported or emulated platform), __init__ raises immediately rather than silently falling back to symlink-vulnerable path handling. This is an intentional hard failure to preserve the symlink-attack guarantees of the writer.
Solutions
- Run the workflow on Linux/macOS where O_NOFOLLOW and O_DIRECTORY exist
- Upgrade Python to a build that exposes these os flags on the target platform
- If you control the code, gate _SafeOutput usage behind a platform check and provide an alternate (less strict) output writer for unsupported platforms
- Avoid emulated Windows environments (MSYS/older WSL) for this workflow
Example fix
# before
out = _SafeOutput(Path('out')) # RuntimeError on Windows
# after
if not hasattr(os, 'O_NOFOLLOW') or not hasattr(os, 'O_DIRECTORY'):
raise SystemExit('tasteforge secure output requires a POSIX platform (Linux/macOS)')
out = _SafeOutput(Path('out')) Defensive patterns
Strategy: try-catch
Validate before calling
import os
platform_ok = hasattr(os, 'O_NOFOLLOW') and hasattr(os, 'O_DIRECTORY')
if not platform_ok:
raise SystemExit('This workflow requires a POSIX platform (Linux/macOS).') Type guard
def supports_secure_output() -> bool:
return hasattr(os, 'O_NOFOLLOW') and hasattr(os, 'O_DIRECTORY') Try / catch
try:
out = _SafeOutput(root)
except RuntimeError as e:
if 'O_NOFOLLOW' in str(e):
sys.exit('Unsupported platform: secure output needs O_NOFOLLOW/O_DIRECTORY')
raise Prevention
- Run this workflow only on Linux/macOS
- Check hasattr(os, 'O_NOFOLLOW') at startup, before long work
- Avoid Windows/MSYS environments for security-sensitive file writes
- Document the POSIX requirement in your pipeline setup
When it happens
Trigger: Instantiating _SafeOutput(root) on a Python build/platform lacking os.O_NOFOLLOW or os.O_DIRECTORY (e.g. Windows, some embedded or exotic platforms where os does not expose these flags).
Common situations: Running the tasteforge workflow on Windows or a minimal cross-compiled Python; very old Python versions; restricted sandboxes that strip os constants.
Understand the failure class
Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.
Related errors
- artifact must be a resident regular file
- artifact path must be canonical and absolute
- gate.variant_invalid
- Memory destination changed while it was being created.
- output artifact must be a regular file
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/2344beb83d3286d6.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/workflow.py:174
samples = measured.get("style_samples", [])
if not isinstance(samples, list) or any(
not isinstance(sample, dict)
or not _finite_real(sample.get("time"))
or float(cast(float, sample["time"])) < 0
or float(cast(float, sample["time"])) > float(duration)
for sample in samples
):
raise ValueError("reference style evidence times must be finite and within duration")
return float(duration)
class _SafeOutput:
"""Descriptor-bound output tree with no-follow traversal and atomic writes."""
def __init__(self, root: Path) -> None:
self._root_fd = -1
if not hasattr(os, "O_NOFOLLOW") or not hasattr(os, "O_DIRECTORY"):
raise RuntimeError("secure output requires O_NOFOLLOW and O_DIRECTORY")
if root.exists() or root.is_symlink():
metadata = root.lstat()
if stat.S_ISLNK(metadata.st_mode):
raise ValueError("output root must not be a symlink")
if not stat.S_ISDIR(metadata.st_mode):
raise ValueError("output root must be a directory")
else:
if not root.parent.is_dir():
raise ValueError("output parent directory must already exist")
root.mkdir(mode=0o700)
self.root = root
self._root_fd = os.open(root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
self._written: list[str] = []
def close(self) -> None:
if self._root_fd >= 0:
os.close(self._root_fd)
self._root_fd = -1View on GitHub (pinned to 8321021c54)