[BUG] Slash commands only load first file on ExFAT filesystems due to JavaScript Number precision loss

Resolved 💬 17 comments Opened Dec 13, 2025 by leonardkore Closed Feb 27, 2026
💡 Likely answer: A maintainer (whyuan-cc, contributor) responded on this thread — see the highlighted reply below.

Summary

Claude Code v2.0.69 only discovers one slash command from .claude/commands/ on ExFAT-formatted drives, despite multiple .md files being present. This is a silent failure - no error messages, remaining commands simply don't load.

Root Cause

JavaScript Number type cannot represent ExFAT's 64-bit inodes precisely (53-bit precision limit). All inodes round to the same value, causing inode-based deduplication to treat distinct files as duplicates.

Debug logs show:

[DEBUG] Skipping duplicate file 'second.md' (same inode already loaded)
[DEBUG] Skipping duplicate file 'third.md' (same inode already loaded)

Actual inode values:

$ stat -f "%N: inode %i" /Volumes/LaCie/.claude/commands/*.md
first.md:  inode 18446744073709450884
second.md: inode 18446744073709450881  # DIFFERENT
third.md:  inode 18446744073709450878  # DIFFERENT

Node.js without BigInt (broken):

const stats = fs.statSync('first.md');
console.log(stats.ino);  // 18446744073709451000

const stats2 = fs.statSync('second.md');
console.log(stats2.ino); // 18446744073709451000 (SAME - precision lost!)

All inodes > 2^53 round to the same Number value.

The Fix

Add { bigint: true } to all fs.statSync() calls in file loading code:

// BEFORE (broken):
const stats = fs.statSync(filePath);
const inode = stats.ino;

// AFTER (fixed):
const stats = fs.statSync(filePath, { bigint: true });
const inode = stats.ino.toString();  // BigInt preserves precision

Affected areas:

  • Slash command loading (.claude/commands/)
  • Agent loading (.claude/agents/)
  • Skill loading (.claude/skills/)
  • Any file discovery using stats.ino for deduplication

Reproduction Steps

Prerequisites: ExFAT-formatted external drive

# 1. Create test directory on ExFAT drive
mkdir -p /Volumes/YOUR_EXFAT_DRIVE/.claude/commands

# 2. Create multiple command files
printf '%s\n' '---' 'description: First' '---' '# First' > /Volumes/YOUR_EXFAT_DRIVE/.claude/commands/first.md
printf '%s\n' '---' 'description: Second' '---' '# Second' > /Volumes/YOUR_EXFAT_DRIVE/.claude/commands/second.md
printf '%s\n' '---' 'description: Third' '---' '# Third' > /Volumes/YOUR_EXFAT_DRIVE/.claude/commands/third.md

# 3. Start Claude Code
cd /Volumes/YOUR_EXFAT_DRIVE
claude

# 4. Check commands
/help  # Only shows /first - /second and /third missing!

Expected: All 3 commands appear
Actual: Only first command appears, others silently skipped

Impact

Severity: CRITICAL

Affected systems:

  • ExFAT (external drives, USB sticks) - CONFIRMED on macOS
  • ⚠️ FAT32 - Likely affected (same inode behavior)
  • NTFS - Unknown (64-bit inodes, needs testing)
  • APFS/HFS+/ext4 - NOT affected (inodes < 2^53)

Cross-platform: Likely affects Windows and Linux (Node.js behavior is consistent)

Affected features:

  • Multiple slash commands
  • Multiple agents
  • Multiple skills
  • Any multi-file discovery using inode deduplication

Workaround

Option 1: Move project to APFS/internal drive (Mac)

cp -R /Volumes/ExFATDrive/project ~/Desktop/project
cd ~/Desktop/project
claude

Option 2: Use subdirectories (works despite bug)

mkdir -p .claude/commands/db
mv .claude/commands/*.md .claude/commands/db/
# Commands become namespaced (e.g., /db:command-name)

Environment

  • Claude Code: v2.0.69
  • OS: macOS Sequoia 15.5 (24F74)
  • Filesystem: ExFAT (external drive)
  • Node.js: v25.2.1 (bundled)
  • Mount: fskit, noatime, noowners

Additional Notes

  • Node.js 10.5.0+ required for { bigint: true } option (already met by Claude Code v2.0.69)
  • BigInt returns signed values (may appear negative for high inodes) - this is correct, values remain unique
  • macOS Sequoia moved ExFAT to user-space (fskit), which may contribute to high inode values
  • No related issues found mentioning ExFAT or inode precision

---

View original on GitHub ↗

17 Comments

Valriz · 7 months ago

This is a (pretty bloody big) problem in windows as well; I'm getting Skill load failures specifically:

"Skipping duplicate skill 'X' from projectSettings (same inode already loaded from projectSettings)" - even though the files have unique NTFS file IDs.

The issue is caused by JavaScript floating-point precision loss when fs.statSync() returns inode values as regular numbers instead of BigInts.

Claude generated report details follow:

Environment

  • OS: Windows 11
  • Node.js: v24.11.0
  • Claude Code: Latest (installed via npm)
  • File System: NTFS

Reproduction

  1. Create multiple skills in .claude/skills/ (e.g., 30+ skill folders)
  2. Enable debug logging
  3. Start Claude Code
  4. Observe debug log showing all but one skill skipped as "duplicate inode"

NTFS file IDs are 64-bit integers. When files are created sequentially, they receive sequential file IDs. In my case:

5764607523036429244  (csharp-legacy)
5764607523036429245  (csharp-modern)
5764607523036429246  (jira)
...etc

Node.js fs.statSync() returns ino as a JavaScript number (IEEE 754 double). While these values are below Number.MAX_SAFE_INTEGER, doubles can only represent ~15-17 significant digits accurately. These 19-digit numbers lose precision in the last few digits:

// All these evaluate to the same number due to precision loss:
5764607523036429244 === 5764607523036429000  // true
5764607523036429245 === 5764607523036429000  // true

Proof via Node.js:

// Without bigint - all report same inode:
const fs = require('fs');
fs.readdirSync('.claude/skills').forEach(s => {
  const stat = fs.statSync(`.claude/skills/${s}/SKILL.md`);
  console.log(s, stat.ino);  // All show: 5764607523036429000
});

// With bigint - correct unique values:
fs.readdirSync('.claude/skills').forEach(s => {
  const stat = fs.statSync(`.claude/skills/${s}/SKILL.md`, {bigint: true});
  console.log(s, stat.ino.toString());  // Shows unique sequential values
});

Debug Log Evidence

Loading skills from directories: managed=..., user=..., project=[D:\..\.claude\skills]
Skipping duplicate skill 'csharp-modern' from projectSettings (same inode already loaded from projectSettings)
Skipping duplicate skill 'jira' from projectSettings (same inode already loaded from projectSettings)
[...30 more skills skipped...]
Deduplicated 31 skills (same inode via symlinks or parent directories)
Loaded 1 unique skills (managed: 0, user: 0, project: 32, duplicates removed: 31)

Suggested Fix

Use {bigint: true} option when calling fs.statSync() for inode comparison:

// Before:
const stat = fs.statSync(filePath);
const inode = stat.ino;

// After:
const stat = fs.statSync(filePath, { bigint: true });
const inode = stat.ino.toString();  // or use BigInt comparison

This option is available in Node.js 10.5.0+ and returns ino as a BigInt, preserving full 64-bit precision.

Workarounds

None that are practical. The inode values are determined by NTFS and cannot be controlled by users. Recreating files might land them in a different range, but this is not reliable.

Impact

  • All but one skill is silently ignored
  • Users may not realise their skills aren't loading
  • Only visible with debug logging enabled
jreisch02 · 6 months ago

Bumping this thread to say we're seeing the exact same issue across multiple users on our Alma Linux 9 based machines with home directories that are on NFS mounts. The symptoms are the same as described here by @Valriz and @leonardkore: the debug log shows incorrectly that multiple skills and/or slashcommands have the same inode, and have been deduplicated. As a result they do not load and are not available in the Claude Code session. Based on the two previous reports, this issue seems to affect any platform, MacOS, Windows, or Linux (which I am confirming with this post).

This is a critical issue for our power users who are developing Skills and SlashCommands in advance of rolling out Claude Code more broadly to our company. We can try rolling back to a version <2.0.69 or suggesting the nested directory workaround temporarily until this is resolved, but we'd love a real fix for this, so we can take advantage of all the great features and work that's gone into the more recent releases.

jreisch02 · 6 months ago

Additional debugging with Claude Code found that as a workaround symlinking the skills and commands directory to a filesystem (in my case /tmp) which has less that the overflow number of inodes gets around the issue, but not in a sustainable way that we can roll out across the company.

Further analysis from Claude below largely echoes what the previous authors already mentioned:

Claude Code Skill Deduplication Bug on NFS with 64-bit Inodes

Date: 2025-12-28
Claude Code Version: 2.0.76
Platform: Linux (RHEL 9), NFS filesystem
Node.js Version: v24.3.0
Status: Confirmed, workaround available

Summary

Claude Code incorrectly skips loading valid skills, reporting them as "duplicates" with identical inodes, when the filesystem inodes are actually unique. This occurs on NFS filesystems using 64-bit inode numbers that exceed JavaScript's Number.MAX_SAFE_INTEGER.

Evidence

Debug Log Shows Incorrect Deduplication

[DEBUG] Skipping duplicate skill 'wiki-search' from userSettings (same inode already loaded from userSettings)
[DEBUG] Skipping duplicate skill 'code-search' from userSettings (same inode already loaded from userSettings)
[DEBUG] Skipping duplicate skill 'hello' from userSettings (same inode already loaded from userSettings)
[DEBUG] Deduplicated 3 skills (same inode)
[DEBUG] Loaded 3 unique skills (managed: 0, user: 5, project: 0, legacy commands: 1)

Result: Only 4 skills available (orchestra-skill, wiki-edit, skill-creator, agent-sdk-dev:new-sdk-app).
Missing: wiki-search, code-search, hello, jr/jr-code-review.

Filesystem Inodes Are Actually Unique

$ stat -c '%i %n' ~/.claude/skills/*/SKILL.md ~/.claude/commands/*.md

606178395885024980  orchestra-skill/SKILL.md
9254609261015347780 code-search/SKILL.md
9877340871519570457 skill-creator/SKILL.md
9254609239525773223 wiki-edit/SKILL.md
9254609261025669606 wiki-search/SKILL.md
9254609261006012866 jr/jr-code-review/SKILL.md
9254609260995448549 hello.md

All 7 inodes are distinct at the filesystem level.

Node.js Reads Correctly But With Precision Loss

// Node.js fs.statSync() returns these values (precision lost in lower bits):
orchestra-skill:     606178395885025000   // Diff: 20
code-search:         9254609261015347000  // Diff: 780
skill-creator:       9877340871519570000  // Diff: 457
wiki-edit:           9254609239525773000  // Diff: 223
wiki-search:         9254609261025670000  // Diff: 394
jr/jr-code-review:   9254609261006012000  // Diff: 866
hello:               9254609260995449000  // Diff: 451

Despite IEEE 754 float64 precision loss (Number.MAX_SAFE_INTEGER = 2^53 - 1 = 9,007,199,254,740,991), the resulting values remain unique - there are no actual collisions.

No Collisions in Isolated JavaScript Tests

const seen = new Set();
for (const ino of [606178395885025000, 9254609261015347000, ...]) {
  seen.add(ino);
}
console.log(seen.size);  // Returns 7 - all unique

Map and Set operations work correctly with these values in isolation.

Workaround (Confirmed Working)

Sync skills to a local filesystem with smaller inode numbers and symlink from ~/.claude/.

Test Results

| Configuration | Inode Range | Skills Loaded | Status |
|--------------|-------------|---------------|--------|
| NFS (original) | ~9×10^18 | 4 of 7 | BUG |
| Local XFS | ~10^8 | 7 of 7 | OK |

Workaround Script

Save as ~/.claude/sync-skills-to-local.sh:

#!/bin/bash
# Workaround for Claude Code skill deduplication bug on NFS
# Syncs skills to local filesystem to avoid 64-bit inode issues

LOCAL_DIR="/tmp/$USER-claude-skills"
CLAUDE_DIR="$HOME/.claude"

# Create local directories
mkdir -p "$LOCAL_DIR/skills" "$LOCAL_DIR/commands"

# Sync from NFS source to local (follow symlinks with -L)
if [ -d "$CLAUDE_DIR/skills.source" ]; then
  rsync -aL --delete "$CLAUDE_DIR/skills.source/" "$LOCAL_DIR/skills/"
elif [ -d "$CLAUDE_DIR/skills" ] && [ ! -L "$CLAUDE_DIR/skills" ]; then
  rsync -aL --delete "$CLAUDE_DIR/skills/" "$LOCAL_DIR/skills/"
fi

if [ -d "$CLAUDE_DIR/commands.source" ]; then
  rsync -a --delete "$CLAUDE_DIR/commands.source/" "$LOCAL_DIR/commands/"
elif [ -d "$CLAUDE_DIR/commands" ] && [ ! -L "$CLAUDE_DIR/commands" ]; then
  rsync -a --delete "$CLAUDE_DIR/commands/" "$LOCAL_DIR/commands/"
fi

# Move originals to .source and create symlinks
if [ -d "$CLAUDE_DIR/skills" ] && [ ! -L "$CLAUDE_DIR/skills" ]; then
  mv "$CLAUDE_DIR/skills" "$CLAUDE_DIR/skills.source"
fi
[ -L "$CLAUDE_DIR/skills" ] && rm "$CLAUDE_DIR/skills"
ln -s "$LOCAL_DIR/skills" "$CLAUDE_DIR/skills"

if [ -d "$CLAUDE_DIR/commands" ] && [ ! -L "$CLAUDE_DIR/commands" ]; then
  mv "$CLAUDE_DIR/commands" "$CLAUDE_DIR/commands.source"
fi
[ -L "$CLAUDE_DIR/commands" ] && rm "$CLAUDE_DIR/commands"
ln -s "$LOCAL_DIR/commands" "$CLAUDE_DIR/commands"

echo "Skills synced to $LOCAL_DIR (local filesystem)"

Usage

# Initial setup
chmod +x ~/.claude/sync-skills-to-local.sh
~/.claude/sync-skills-to-local.sh

# Add to ~/.zshrc or ~/.bashrc for persistence across reboots
# (since /tmp is cleared on reboot)
[ -x ~/.claude/sync-skills-to-local.sh ] && ~/.claude/sync-skills-to-local.sh

# Re-run after modifying skills
~/.claude/sync-skills-to-local.sh

Verification

After applying the workaround:

$ claude --print "List all your skills"
# Should show all skills including wiki-search, code-search, hello, etc.

Root Cause Analysis

Key Finding: Upper 32-Bit Collision Pattern

Analysis of the inode structure reveals a critical pattern:

LOADED skills (unique upper 32 bits):
  orchestra-skill:  0x0869940a________
  wiki-edit:        0x806efa12________
  skill-creator:    0x89135d30________

SKIPPED skills (shared upper 32 bits = 0x806efa17):
  code-search:      0x806efa17 01dc3244
  wiki-search:      0x806efa17 0279b1e6
  hello:            0x806efa17 00ac8ee5
  jr-code-review:   0x806efa17 014dc1c2

All skipped skills share the same upper 32 bits (0x806efa17), while loaded skills each have unique upper 32 bits. This suggests the deduplication logic may be:

  • Only comparing upper 32 bits of the inode
  • Using a hash function that collides on these values
  • Experiencing endianness issues combined with truncation

Likely Buggy Code Pattern

The collision pattern strongly suggests code like:

// BUGGY: Only compares upper 32 bits
const key = Math.floor(inode / 0x100000000);

// Or attempting bitwise shift (broken for 64-bit values!)
const key = inode >> 32;  // JS bitwise ops are 32-bit only!

Note on JavaScript bitwise operators: All bitwise operators (>>, <<, |, &, ^) convert operands to 32-bit signed integers first. Using inode >> 32 on a 64-bit value does NOT extract the upper 32 bits—it produces garbage because the 64-bit value is truncated to 32 bits before shifting.

System Context

  • Endianness: Little-endian (x86_64 Linux)
  • Node.js stat: Returns inode as float64 Number (loses ~10 bits of precision on large values)
  • The float64 values remain unique, but any code that extracts only upper 32 bits will see collisions

Possible Root Causes

  1. Upper 32-bit extraction - Using Math.floor(inode / 2^32) as dedup key
  2. Broken bitwise shift - Using inode >> 32 (JavaScript bitwise is 32-bit only)
  3. Hash function - Internal hash that primarily uses upper bits
  4. Composite key bug - Building device:inode key but truncating inode

Why NFS Inodes Have This Structure

NFS 64-bit inodes typically encode filesystem metadata in the upper bits:

  • Upper 32 bits: Often contain file handle components (server ID, volume ID, etc.)
  • Lower 32 bits: Contain the actual inode number within the volume

Files on the same NFS volume/server frequently share identical upper 32 bits, differing only in lower bits. Any deduplication logic that ignores or truncates the lower 32 bits will cause false collisions.

What's NOT the cause

  • Actual inode collisions (inodes are unique at filesystem level)
  • JavaScript Number precision loss alone (values remain distinct after float64 conversion)
  • JSON serialization (round-trips correctly)
  • Set/Map data structure behavior (works correctly in isolation)

Recommended Fix for Claude Code

Use BigInt for exact inode handling:

// Current (buggy) - precision loss on large inodes:
const stat = fs.statSync(path);
const key = stat.ino;

// Fixed - exact values preserved:
const stat = fs.statSync(path, { bigint: true });
const key = `${stat.dev}:${stat.ino}`;  // String key for Map/Set

Reproduction Steps

  1. Use an NFS filesystem with 64-bit inode support (inodes > 2^53)
  2. Create multiple skills in ~/.claude/skills/
  3. Run Claude Code and check available skills
  4. Observe debug log (--debug) showing skills skipped as "same inode"
  5. Verify with stat -c '%i' that filesystem inodes are actually unique
  6. Apply workaround (sync to local disk) and confirm all skills load

Impact

  • Users on NFS filesystems may have skills silently ignored
  • No clear error message - misleading "same inode" debug output
  • Affects enterprise environments where home directories are commonly NFS-mounted
  • Workaround requires manual intervention and maintenance
ctung · 6 months ago

This is impacting us as well on NFS. The workaround until this gets fixed

  • Move your skill to /tmp which is a local disk and does not have NFS sized inodes
  • link your .claude/skills/my-skill -> /tmp/my-skill
pmuller · 6 months ago

I hit the exact same issue in NFS: some skills were missing due to deduplication based on "identical" inodes.

Workaround: copy files until they get non overlapping inodes.

jreisch02 · 6 months ago

@whyuan-cc : would it be possible to get an update on this bug as well? You were supremely helpful in updating us on the closed out issue here: https://github.com/anthropics/claude-code/issues/11912.

I was expecting to see this one in 2.1.1 today, looks like it still has issues. From the number of github-actions referencing this issue, it seems many folks are running into this.

Thanks so much!

whyuan-cc contributor · 6 months ago

Thanks for reporting. I have a PR to fix this.

whyuan-cc contributor · 6 months ago

This was fixed. Please reopen if you can repro with build this week.

hongping · 5 months ago

may we know when will this be released? It is quite painful for NFS users...

hongping · 5 months ago

@whyuan-cc based on CHANGELOG, this should be fixed in 2.1.3, but I am still seeing same issue in 2.1.12 for Linux build. Can you help to check the repo head?

whyuan-cc contributor · 5 months ago

@hongping could you kindly run

 claude --version                                                                                                                                                         
  strings $(which claude) | grep "lstatSync.*bigint"                                                                                                                       
  If the bigint string is there but issue persists, ask for:                                                                                                            
    - Debug logs showing the actual inode values being compared                                                                                                            
    - Output of stat -c '%i' ~/.claude/skills/*/SKILL.md to see actual inodes                
    
hongping · 5 months ago

@whyuan-cc sure. I will update the output once I able to access my laptop. But just one observation, I don't see this problem when running Claude Code by installing through npm. However, if I install Claude Code through native install (the curl thingy), the issue persist.

hongping · 5 months ago

@whyuan-cc based on the native build claude-2.1.12-linux-x64, the lstatSync.*bigint is there but issue persist. Based on debug log, it is complaining the inode is the same for the second md file in ~/.claude/commands.

stat -c '%i' ~/.claude/commands/* show
9275036499177641659
9275036499148460796

npm installation of claude code does not show the issue.

whyuan-cc contributor · 5 months ago

thanks for confirming. It is native build issue and we will fix it.

github-actions[bot] · 4 months ago

Closing for now — inactive for too long. Please open a new issue if this is still relevant.

itaylev20 · 4 months ago

@whyuan-cc this issue is still persistent on claude code 2.1.66

github-actions[bot] · 4 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.