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

  1. Pass an explicit safe output path via the --out CLI flag to bypass name-derived defaults.
  2. Fix the pack name/genre in the operator-authored JSON to match the allowed pattern (single path component, no separators).
  3. Inspect _OUTPUT_COMPONENT in cli.py and conform the value to exactly that regex.
  4. 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

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


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)