affaan-m/ECC · error · ValueError
output artifact must be a regular file
Error message
output artifact must be a regular file: {relative} What it means
Before replacing an artifact, write_json stats the existing name (no symlink following) and refuses to overwrite anything that is not a regular file. This prevents clobbering directories, FIFOs, sockets, or symlinks placed at the artifact path.
Solutions
- Remove or replace the non-regular entry at the artifact path, then re-run
- Point the workflow at a fresh, trusted output directory
- If a symlink is intentional, copy the target's content into a real file at the artifact path instead
Example fix
# before: output/manifest.json is a symlink # $ rm output/manifest.json $ ls -l output/manifest.json # verify regular file after rerun # after: workflow writes a fresh regular file
Defensive patterns
Strategy: validation
Validate before calling
import os, stat
target = Path(output_root) / relative
if target.exists() or target.is_symlink():
st = os.lstat(target)
assert stat.S_ISREG(st.st_mode), f"{target} is not a regular file" Type guard
def is_regular_file_or_absent(path: Path) -> bool:
if not (path.exists() or path.is_symlink()):
return True
import os, stat
return stat.S_ISREG(os.lstat(path).st_mode) Try / catch
try:
writer.write_json(relative, payload)
except ValueError:
target = Path(output_root) / relative
if target.is_symlink() or (target.exists() and not target.is_file()):
target.unlink()
writer.write_json(relative, payload)
else:
raise Prevention
- Write artifacts only into trusted, non-shared directories
- Audit output directories for symlinks before running in multi-user environments
- Avoid pointing output root at /tmp or other shared locations
When it happens
Trigger: write_json targets a path where an existing entry is a directory, FIFO, device, or symlink; e.g. 'output/manifest.json' is a symlink planted by an attacker or a leftover directory.
Common situations: Attacker-controlled output directories with symlink preplants; a previous version of the tool created a directory at the artifact name; users manually linked outputs to shared locations.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- gate.variant_invalid
- output bundle contains a symlink
- output bundle root must not be a symlink
- output destination must not be a symlink
- output directory must not be a symlink
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/19777bdaed6826ed.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/workflow.py:249
opened.append(self._open_dir((directory,), create=True))
finally:
for descriptor in opened:
os.close(descriptor)
def write_json(self, relative: str, payload: Any) -> None:
path = Path(relative)
if path.is_absolute() or not path.name or any(part in {".", ".."} for part in path.parts):
raise ValueError("artifact path must stay beneath output root")
parent_fd = self._open_dir(tuple(path.parts[:-1]), create=False)
temporary = f".{path.name}.tmp-{secrets.token_hex(8)}"
descriptor = -1
try:
try:
existing = os.stat(path.name, dir_fd=parent_fd, follow_symlinks=False)
except FileNotFoundError:
existing = None
if existing is not None and not stat.S_ISREG(existing.st_mode):
raise ValueError(f"output artifact must be a regular file: {relative}")
data = json.dumps(payload, indent=2, sort_keys=True).encode("utf-8") + b"\n"
descriptor = os.open(
temporary,
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o600,
dir_fd=parent_fd,
)
view = memoryview(data)
while view:
written = os.write(descriptor, view)
view = view[written:]
os.fsync(descriptor)
os.close(descriptor)
descriptor = -1
os.replace(temporary, path.name, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
os.fsync(parent_fd)
if relative not in self._written:
self._written.append(relative)View on GitHub (pinned to 8321021c54)