[BUG] Filesystem write_file tool does not preserve file permissions
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?
Summary
The Filesystem:write_file tool resets file permissions to a default (likely 644) when writing, even when overwriting an existing file that has non-default permissions set. This causes silent breakage when the tool is used to update executable scripts.
Permissions are reset to 644 on every write, stripping the execute bit. The file is silently non-executable.
Impact
- Shell scripts updated via write_file silently lose their execute bit
- The failure mode is downstream and non-obvious: the script exists, the content is correct, but it won't run
- In this specific case:
~/Developer/claude-wrapper/bin/claude-wrapper(a symlink target) had +x stripped, causingclaude(which symlinks to it) to fail silently on the next invocation - This has caused repeated debugging sessions across multiple conversations
What Should Happen?
write_file should preserve the existing file's permission bits when overwriting. If no prior file exists, 644 is a reasonable default.
Error Messages/Logs
Steps to Reproduce
- Create an executable script:
``bash``
echo '#!/usr/bin/env bash\necho hello' > ~/test.sh
chmod +x ~/test.sh
ls -l ~/test.sh # -rwxr-xr-x
- Use
Filesystem:write_fileto update the file content - Check permissions:
``bash``
ls -l ~/test.sh # -rw-r--r-- — execute bit gone
Claude Model
Sonnet (default)
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.104
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Suggested Fix
Before writing, stat the existing file and restore its permission bits after the write:
import os, stat
existing_perms = stat.S_IMODE(os.stat(path).st_mode) if os.path.exists(path) else 0o644
# ... write file ...
os.chmod(path, existing_perms)
Or alternatively, expose an optional permissions parameter so callers can explicitly set mode bits when they know the file should be executable.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗