affaan-m/ECC · error · ValueError
must match to name an output file; pass --out
Error message
{what} must match {_OUTPUT_COMPONENT.pattern} to name an output file; pass --out What it means
_output_component derives a default output filename from operator-authored pack names or genres. Because the value becomes a path component, it must be a plain string matching the manifest schema's safe-component pattern (_OUTPUT_COMPONENT) — no separators or traversal. The library throws this ValueError to block path traversal via untrusted JSON fields.
Solutions
- Pass an explicit safe output path via the --out CLI flag to bypass name-derived defaults.
- Fix the pack name/genre in the operator-authored JSON to match the allowed pattern (single path component, no separators).
- Inspect _OUTPUT_COMPONENT in cli.py and conform the value to exactly that regex.
- Quote/escape shell input when generating JSON so values don't pick up slashes or whitespace.
Example fix
// before
{name: "../packs/my pack"}
tasteforge distill pack.json
// after
{name: "my-pack"}
tasteforge distill pack.json # or: tasteforge distill pack.json --out out/my-pack.tz Defensive patterns
Strategy: validation
Validate before calling
import re
_OUTPUT_COMPONENT = re.compile(r'[A-Za-z0-9._-]+')
assert isinstance(name, str) and _OUTPUT_COMPONENT.fullmatch(name), f"pass --out for {name!r}" Type guard
def is_safe_component(value) -> bool:
return isinstance(value, str) and bool(_OUTPUT_COMPONENT.fullmatch(value)) Try / catch
try:
run_distill(args)
except ValueError as e:
if 'must match' in str(e) and 'pass --out' in str(e):
sys.exit(f"Invalid name in pack JSON: {e}. Re-run with --out <path>.")
raise Prevention
- Pass --out explicitly whenever pack names are not fully trusted
- Keep pack/genre names limited to [A-Za-z0-9._-]
- Never allow path separators or '..' in name-derived output fields
- Add a pre-flight regex check on names when generating pack JSON
When it happens
Trigger: Running `tasteforge distill` or `tasteforge apply` where the pack name/genre string is non-str (e.g. parsed as int), empty, or contains characters outside the allowed pattern such as '/', '\\', '..', or spaces.
Common situations: Pack JSON authored with a name like '../exfil' or 'sub/dir/pack', names containing spaces or unicode punctuation, or YAML/JSON where the field parses to a number instead of a string.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- output must have a video suffix distinct from…
- Path does not exist
- all overlays must be readable local files
- all takes must be readable local files
- application bundle differs from its bound evidence
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/ef0ffbc389f63dfa.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/cli.py:80
profile = interview_mod.conduct(answers, genre=args.genre)
out = Path(args.out) if args.out else Path(f"{args.genre}-profile.json")
out.write_text(json.dumps(profile, indent=2), encoding="utf-8")
print(out)
return EXIT_OK
_OUTPUT_COMPONENT = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
def _output_component(value: Any, what: str) -> str:
"""Return ``value`` only when it is safe to use as an output filename part.
Pack names and genres come from operator-authored JSON. They are only
used to derive default output paths, so they must never carry path
separators or traversal; the same pattern the manifest schema declares.
"""
if not isinstance(value, str) or not _OUTPUT_COMPONENT.fullmatch(value):
raise ValueError(
f"{what} must match {_OUTPUT_COMPONENT.pattern} to name an output file; pass --out"
)
return value
def cmd_distill(args: argparse.Namespace) -> int:
if args.live:
print(distill_mod._FAIL_CLOSED, file=sys.stderr)
return EXIT_FAIL_CLOSED
profile = json.loads(Path(args.profile).read_text(encoding="utf-8"))
sp = pack_mod.load(args.pack) if args.pack else None
spec = distill_mod.distill_local(profile, sp)
out = (Path(args.out) if args.out
else Path(f"{_output_component(profile.get('genre', 'spec'), 'profile genre')}-spec.json"))
out.write_text(json.dumps(spec, indent=2), encoding="utf-8")
print(out)
return EXIT_OK
View on GitHub (pinned to 8321021c54)