[BUG] Inode INT overflow preventing loading mutiple skills on NFS
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Claude Code Skill Deduplication Bug: Large Inode Number Precision Loss
Summary
Claude Code incorrectly deduplicates skills on filesystems with large inode numbers due to JavaScript's floating-point precision limitations. Skills with different inodes are incorrectly identified as duplicates, causing valid skills to be skipped.
Affected Versions
- Claude Code 2.0.76 (confirmed)
- Likely affects all versions using the current inode-based deduplication logic
Symptoms
When loading skills from .claude/skills/, you may see debug messages like:
[DEBUG] Skipping duplicate skill 'skill-creator' from projectSettings (same inode already loaded from projectSettings)
[DEBUG] Deduplicated 1 skills (same inode via symlinks or parent directories)
[DEBUG] Loaded 1 unique skills (managed: 0, user: 0, project: 2, duplicates removed: 1)
Despite having multiple distinct skills with different inodes, only one skill is loaded.
Root Cause
The bug is in three locations in cli.js:
Function 1: j77 (sync version, around line 3901)
function j77(A) {
let Q = jA();
try {
let B = Q.lstatSync(A);
return `${B.dev}:${B.ino}` // B.ino is a Number
} catch {
return null
}
}
Function 2: c27 (async version, around line 3161)
async function c27(A) {
try {
let Q = await m27(A); // m27 is lstat from fs/promises
return `${Q.dev}:${Q.ino}` // Q.ino is a Number
} catch {
return null
}
}
Location 3: SC9.lstatSync filesystem wrapper (around line 9)
// The filesystem abstraction wrapper ignores additional arguments!
lstatSync(A) {
return oI("lstatSync", () => l9.lstatSync(A)) // Options not passed through
}
This wrapper is critical - even if you add {bigint:true} to the j77 function call, the wrapper discards it. The wrapper must be patched to pass through the options argument.
The issue:
lstatSync().inoreturns the inode as a JavaScriptNumber- JavaScript Numbers are 64-bit floats with only 53 bits of integer precision
Number.MAX_SAFE_INTEGER= 9007199254740991- On filesystems (especially NFS) with large inode numbers exceeding this limit, precision is lost
- Different inodes get rounded to the same value, causing false duplicate detection
Example
| Skill | Actual Inode | JavaScript Number |
|-------|--------------|-------------------|
| slide-creator | 9275028862687879457 | 9275028862687880000 |
| skill-creator | 9275028862687879475 | 9275028862687880000 |
Both inodes exceed MAX_SAFE_INTEGER and round to the same value, causing Claude Code to think they're the same file.
How to Replicate
- Use a filesystem with large inode numbers (common on enterprise NFS mounts)
- Create two or more skills in
.claude/skills/:
````
.claude/skills/
├── skill-a/
│ └── SKILL.md
└── skill-b/
└── SKILL.md
- Verify the inodes exceed
Number.MAX_SAFE_INTEGER:
``bash``
stat .claude/skills/*/SKILL.md | grep Inode
# If inode > 9007199254740991, bug will trigger
- Run Claude Code with debug logging enabled
- Observe that one skill is incorrectly skipped as a "duplicate"
Verification Script
node -e "
const ino1 = 9275028862687879457;
const ino2 = 9275028862687879475;
console.log('MAX_SAFE_INTEGER:', Number.MAX_SAFE_INTEGER);
console.log('ino1 === ino2:', ino1 === ino2); // true (incorrect!)
"
How It Was Found
- Observed debug log showing skill-creator being skipped as duplicate
- Verified both skills exist with different inodes using
stat - Traced the deduplication code in
cli.js - Identified that inode numbers exceeded JavaScript's safe integer range
- Confirmed precision loss causes false equality in Node.js
Workaround
- Move the skill to local disk: move the skill to the /tmp directory
- Point to the local disk: create a symlink from ./.claude/skills/my-skill -> /tmp/my-skill
- Local disk will have smaller inodes than NFS, which won't overrun the INT datatype
Environment Details
- Platform: Linux (RHEL 8)
- Filesystem: NFS (enterprise storage)
- Node.js: 24.x
- Inode range: >9 quintillion (9275028862687879xxx)
What Should Happen?
should see multiple skills when you have multiple skills in the .claude/skills/ folder
instead, you only see one, and all others are skipped because of de-duplication during startup
Error Messages/Logs
## Symptoms
When loading skills from `.claude/skills/`, you may see debug messages like:
[DEBUG] Skipping duplicate skill 'skill-creator' from projectSettings (same inode already loaded from projectSettings)
[DEBUG] Deduplicated 1 skills (same inode via symlinks or parent directories)
[DEBUG] Loaded 1 unique skills (managed: 0, user: 0, project: 2, duplicates removed: 1)
Steps to Reproduce
How to Replicate
- Use a filesystem with large inode numbers (common on enterprise NFS mounts)
- Create two or more skills in
.claude/skills/:
````
.claude/skills/
├── skill-a/
│ └── SKILL.md
└── skill-b/
└── SKILL.md
- Verify the inodes exceed
Number.MAX_SAFE_INTEGER:
``bash``
stat .claude/skills/*/SKILL.md | grep Inode
# If inode > 9007199254740991, bug will trigger
- Run Claude Code with debug logging enabled
- Observe that one skill is incorrectly skipped as a "duplicate"
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.0.76
Platform
Other
Operating System
Other Linux
Terminal/Shell
Xterm
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗