`IO.FS.realPath` reports every failure as `noFileOrDirectory` instead of classifying by `errno`
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 66/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- cpp
- Domain
- operating-systems
Research direction
The bug is in the Unix branch of lean_io_realpath in src/runtime/io.cpp, which calls mk_file_not_found_error (hardcoded ENOENT) instead of decode_io_error(errno, filename) as the other primitives do, such as readFile near line 424. Start there and check the Windows branch around line 1025, which ignores GetLastError(). Build the Repro.lean case from the issue to confirm ENOTDIR, ENAMETOOLONG and ELOOP. Done when realPath reports the same errno-based kinds as readFile on those paths.
Written by the indexing model from the issue text.
Description
Prerequisites
- Check that your issue is not already filed:
https://github.com/leanprover/lean4/issues - Reduce the issue to a minimal, self-contained, reproducible test case.
Avoid dependencies to Mathlib or Batteries. - Test your test case against the latest nightly release, for example on
https://live.lean-lang.org/#project=lean-nightly
(You can also use the settings there to switch to “Lean nightly”)
Description
IO.FS.realPath maps every realpath(3) failure to IO.Error.noFileOrDirectory (with errno
hardcoded to ENOENT), instead of decoding errno like every other file primitive. Success is fine;
only the error kind is wrong, contradicting the docstring's promise that it "coincides with the POSIX
realpath function".
Context
Code that tells “not found” apart from “permission denied” / “not a directory” / “too many levels of
symbolic links” via IO.Error always gets noFileOrDirectory.
Steps to Reproduce
Repro.lean:
def kind : IO.Error → String
| .noFileOrDirectory _ c _ => s!"noFileOrDirectory(errno={c})"
| .inappropriateType _ c _ => s!"inappropriateType(errno={c})"
| .invalidArgument _ c _ => s!"invalidArgument(errno={c})"
| e => s!"other: {e}"
def errKind (act : IO α) : IO String := do
try
let _ ← act
return "no error"
catch e => return kind e
def case (name p : String) : IO Unit := do
IO.println s!"{name}: realPath {← errKind (IO.FS.realPath p)} | readFile {← errKind (IO.FS.readFile p)}"
def main : IO UInt32 := do
IO.FS.writeFile "rp_file" "x"
case "ENOTDIR " ("rp_file" ++ "/x")
IO.FS.removeFile "rp_file"
case "ENAMETOOLONG " (String.ofList (List.replicate 300 'n'))
let _ ← IO.Process.output { cmd := "sh", args := #["-c", "rm -f rp_loop; ln -s rp_loop rp_loop"] }
case "ELOOP " "rp_loop"
return 0
lean --run Repro.lean(or compile:lean Repro.lean -c Repro.c && leanc -O1 -o Repro Repro.c && ./Repro).
Expected behavior: realPath reports the same errno-based kind as readFile on the same path
(inappropriateType for ENOTDIR, invalidArgument for ENAMETOOLONG/ELOOP).
Actual behavior: realPath reports noFileOrDirectory(errno=2) for every path:
ENOTDIR : realPath noFileOrDirectory(errno=2) | readFile inappropriateType(errno=20)
ENAMETOOLONG : realPath noFileOrDirectory(errno=2) | readFile invalidArgument(errno=36)
ELOOP : realPath noFileOrDirectory(errno=2) | readFile invalidArgument(errno=40)
Versions
Lean (version 4.36.0-nightly-2026-10-13, x86_64-unknown-linux-gnu, commit b7725c34da98e6e2f6323d7f382448ddf31477fb, Release)
Linux 7.0.0-38-generic (x86_64)
Additional Information
On Unix, lean_io_realpath calls mk_file_not_found_error (io.cpp:1061),
which hardcodes errno = ENOENT (io.cpp:86),
while other primitives use decode_io_error (io.cpp:370, e.g. readFile at io.cpp:424).
Fix: return io_result_mk_error(decode_io_error(errno, filename));. The Windows branch (io.cpp:1025)
has the same problem, ignoring GetLastError().
Impact
Add 👍 to issues you consider important. If others are impacted by this issue, please ask them to add 👍 to it.
- Dominant language
- Lean
- Stars
- 9.5k
- Forks
- 1k
- Avg merge
- 23h 14m
- Merged PRs (30d)
- 273
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from leanprover/lean4
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
leanprover/lean4#15692 · 3 reactions ·
Maintainers usually reply within 1 day
-
`Child.pid` leaks the `Child` and its pipe file descriptors (owned argument treated as borrowed)Openbug
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
leanprover/lean4#15681 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
leanprover/lean4#15677 ·
Maintainers usually reply within 1 day
-
`Child.takeStdin` loses `setsid`, so `Child.kill` only signals the child, not its process groupOpenbug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
leanprover/lean4#15674 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
leanprover/lean4#15626 ·
Maintainers usually reply within 1 day
All issues in leanprover/lean4
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
bytecodealliance/rustix#1706 · 1 comment ·
Maintainers usually reply within 11 days
-
spawn drops an own `__proto__` environment entryPossibly taken @nikolay-dementiev claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
comp/gateway comp/tools P2 platform/windows sweeper:risk-message-delivery sweeper:risk-platform-windows type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
NousResearch/hermes-agent#138872 ·
Maintainers usually reply within 1 day
-
os: RawConn methods of a closed File return internal/poll.ErrFileClosing instead of ErrClosedPossibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
agent:WSL bug linux LOW
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day