I'd like to take #399 (cc @Anton as the author). Today every Path effect returns PathErr(IOErr), so a failure prints as PathErr(NotFound) with no indication of which path — or which of several calls — failed. The path is already in hand at the failure point, so this is pure Roc in Path.roc; no host or glue changes.
Two things I'd like to agree before writing it.
1. Positional or record payload? The platform has both:
PathErr(IOErr, Path) # like Sqlite's SqliteErr(ErrCode, Str)
PathErr({ path : Path, err : IOErr }) # like Cmd's FailedToGetExitCode({ command, err })
I lean positional, for one concrete reason: Err(PathErr(NotFound, _)) still works, so the common one-liner survives — including in the platform's own is_file! / is_dir! / exists!. With a record those become full destructures (I couldn't find any { field, .. } partial patterns in the repo).
2. Include the operation as well? The triage note mentions "operation/path context", but I lean toward path-only: it costs every signature, and it only adds information when two different operations hit the same path. Happy to include it if you'd rather have it.
Either way this is a breaking change, though a narrow one — ?, ? |err| …, ?? and Err(_) all keep working, since the error type is open and inferred. Only an explicit destructure of the inner tag breaks: 6 spots in the platform and 6 lines across two examples, which I'd update in the same PR.
(Sqlite.prepare! deliberately keeps path/query out of SqliteErr, so "leave it as-is" is a legitimate answer too — I'd rather hear that now than after the PR.)
Put it up as #468, implementing the positional option — easier to judge the shape against real code, and switching to a record is a mechanical pass either way.
One question while you're there: File.open_reader! takes a Path but returns a bare FileErr, so whether an open error names the path would depend on which module you called. Fold it in, or follow-up?
- Positional or record payload?
Positional looks good
Include the operation as well?
Let's try without for now, we can always change it later.
File.open_reader!takes aPathbut returns a bareFileErr, so whether an open error names the path would depend on which module you called. Fold it in, or follow-up?
Let's add the path there too in the same PR.
Done in #468 — one wrinkle on the File one: FileErr(err, path) doesn't work, since read_line! keeps returning FileErr(IOErr) (a Reader has no path) and one tag name can't have two arities. So open_reader! reports PathErr(err, path) instead, which also means an open and a Path op unify in one ? chain. Shout if you'd rather keep the FileErr name and change Reader instead.
Last updated: Aug 12 2026 at 12:35 UTC