Repository navigation
AIX: clang libc++ std::filesystem::remove_all() Returns Wrong Error Code #62790
Description
Summary
On AIX with clang/libc++, std::filesystem::remove_all() incorrectly returns error code 17 (EEXIST - "File exists") instead of 13 (EACCES - "Permission denied") when encountering permission errors during recursive directory removal. This doesn't happen using gcc/libstdc++ on the same machine.
Note: This bug only manifests when running as a non-root user. When run as root, the operation succeeds (no permission error occurs), so the bug is not triggered.
Environment
AIX (Bug Present)
- OS: AIX 7.2
- Compiler: clang version 20.1.7 (powerpc-ibm-aix7.2.0.0)
- Target: powerpc-ibm-aix7.2.0.0
- Standard Library: libc++
AIX with GCC (Correct Behavior)
- OS: AIX 7.2
- Compiler: GCC 12.3.0
- Standard Library: libstdc++
- Result: Bug does NOT occur with GCC on AIX
Also tested behavior on Linux:
Linux (Correct Behavior)
- OS: Fedora 43
- Compiler: clang version 20.1.8 (Fedora 20.1.8-2.fc43)
- Target: x86_64-redhat-linux-gnu
- Standard Library: libc++
Expected Behavior
When std::filesystem::remove_all() fails due to insufficient permissions:
error.value()should be 13 (EACCES)error.message()should be "Permission denied"error == std::errc::permission_deniedshould be true
Actual Behavior on AIX
error.value()is 17 (EEXIST)error.message()is "File exists"error == std::errc::permission_deniedis false
Note: This bug only manifests when running as a non-root user. When run as root, the operation succeeds (no permission error occurs), so the bug is not triggered.
Impact
This bug causes applications to misinterpret permission errors as "file exists" errors, leading to incorrect error handling.
The Node.js test suite fails on AIX with clang due to this bug:
Test: parallel/test-fs-rm (test case 1918)
Failure:
not ok 1918 parallel/test-fs-rm
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
+ actual - expected
Comparison {
+ code: '',
- code: 'EACCES',
name: 'Error'
}
actual: Error: , Unknown error: File exists '/home/iojs/build/.tmp.1724/rm-11/fs-KFNaea'
at Object.rmSync (node:fs:1206:18)
...
{
errno: -4094,
code: '',
path: '/home/iojs/build/.tmp.1724/rm-11/fs-KFNaea',
syscall: 'rm'
},
expected: { code: 'EACCES', name: 'Error' }
Reproducer
Test Program
Save the following as test-clang-fs-removeall-bug.cpp:
/*
* Bug: std::filesystem::remove_all() returns EEXIST instead of EACCES on AIX
*
* Expected: error.value() == 13 (EACCES - Permission denied)
* Actual on AIX: error.value() == 17 (EEXIST - File exists)
*/
#include <iostream>
#include <filesystem>
#include <system_error>
#include <sys/stat.h>
#include <errno.h>
int main() {
// Create directory structure: root/middle/leaf
std::filesystem::create_directories("root/middle/leaf");
// Make middle read-only to trigger permission error
chmod("root/middle", 0555);
// Try to remove_all (should fail with permission error)
std::error_code error;
std::filesystem::remove_all("root", error);
// Report results
std::cout << "EACCES = " << EACCES << std::endl;
std::cout << "EEXIST = " << EEXIST << std::endl;
std::cout << "error.value() = " << error.value() << std::endl;
std::cout << "error.message() = \"" << error.message() << "\"" << std::endl;
std::cout << "error == std::errc::permission_denied: "
<< (error == std::errc::permission_denied ? "YES" : "NO") << std::endl;
// Cleanup
chmod("root/middle", 0755);
std::filesystem::remove_all("root");
return error.value() == EEXIST ? 1 : 0; // Return 1 if bug is present
}Compile and Run
On AIX (as non-root user):
/opt/clang+llvm-20.1.7-powerpc64-ibm-aix-7.2/bin/clang++ -o test-clang-fs-removeall-bug.out test-clang-fs-removeall-bug.cpp
./test-clang-fs-removeall-bug.outOn Linux (as non-root user):
clang++ -o test-clang-fs-removeall-bug.out test-clang-fs-removeall-bug.cpp
./test-clang-fs-removeall-bug.outNote: Must run as non-root user to trigger the permission error.
Output on Linux (clang 20.1.8 - Correct Behavior)
EACCES = 13
EEXIST = 17
error.value() = 13
error.message() = "Permission denied"
error == std::errc::permission_denied: YES
Output on AIX (clang 20.1.7 - Bug Present)
EACCES = 13
EEXIST = 17
error.value() = 17
error.message() = "File exists"
error == std::errc::permission_denied: NO
Verification: This bug has been confirmed across multiple compilers and platforms:
- AIX with clang 20.1.7 + libc++: Bug present (returns EEXIST instead of EACCES)
- Linux with clang 20.1.8 + libc++: Bug NOT present (returns EACCES correctly)
- AIX with GCC 12.3.0 + libstdc++: Bug NOT present (returns EACCES correctly)
This confirms the bug is specific to clang's libc++ implementation on AIX, not a general AIX filesystem issue or a general clang/libc++ bug. The same AIX system works correctly with GCC's libstdc++.
Root Cause Analysis
The bug appears to be in the AIX-specific implementation of std::filesystem::remove_all() in libc++. When the underlying system call (likely rmdir() or unlink()) fails with errno = 13 (EACCES), the error is being incorrectly translated to std::error_code with value 17 (EEXIST).
This issue only occurs when running as a non-root user because:
- Root users bypass permission checks, so no EACCES error occurs
- Non-root users encounter permission errors when trying to remove read-only directories
- The bug in error code translation only manifests when EACCES is actually returned by the system
Workaround
@richardlau created a patch in Node.js to workaround this.
ref: #62788
CC
@nodejs/platform-aix
We need to file this report to AIX compilers team too.
Activity
src: workaround AIX libc++ std::filesystem bug
daeyeon commentedon Apr 18, 2026
Should we close this now that the workaround is in place, or keep it open to track the upstream AIX/libc++ fix?
src: workaround AIX libc++ std::filesystem bug
src: workaround AIX libc++ std::filesystem bug
src: workaround AIX libc++ std::filesystem bug
github-actions commentedon Jul 20, 2026
This issue has been marked as stale due to 90 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
miladfarca commentedon Jul 21, 2026
@abmusse can this issue be closed now?
abmusse commentedon Aug 18, 2026
This has been merged upstream but looks like its only available on new clang versions (Clamg 22+?). Currently AIX is using Clang 20 once we move up to a newer version I think the workaround #62788 can be dropped.
abmusse commentedon Aug 18, 2026
Perhaps we should add some comment in the code that this has been fixed upstream and we could drop the workaround whenever we move up to clang version that fixed this upstream.