When AI tools like Claude Code edit a file, they typically delete it and write a brand new one. The content is correct, but the file’s metadata — creation timestamp, inode, ownership, permissions, ACLs, extended attributes — is silently destroyed. On a personal project this is a minor annoyance. On a production server with specific ownership, SELinux contexts, or ACLs, it can break things.
Here’s a simple protocol you can drop into your AI tool’s instruction file to fix this.
The Problem
Most AI tools (and many text editors) edit files by writing to a temporary file and replacing the original. This means the “edited” file is actually a completely new file with a new inode, new creation time, and default permissions.
Watch what happens when we create a file and then let a tool overwrite it:
=== BEFORE ===
Created: Mar 25 14:08:08 2026
Modified: Mar 25 14:08:08 2026
Inode: 1365599432
=== AFTER (naive write — delete + recreate) ===
Created: Mar 25 14:08:32 2026 <-- creation time CHANGED
Modified: Mar 25 14:08:32 2026
Inode: 1365599651 <-- inode CHANGED (it's a different file)MarkdownThe creation timestamp jumped forward. The inode changed entirely. If this file had special ownership, ACLs, or extended attributes, those are gone too.
The Solution
The trick is to never replace the file. Instead, rename the original aside, work on a copy, then pour the new content back into the original inode using shell redirection (>), which writes into an existing file without creating a new one.
Here’s the same edit using the protocol:
=== BEFORE ===
Created: Mar 25 14:08:09 2026
Modified: Mar 25 14:08:09 2026
Inode: 1365599439
=== AFTER (metadata preservation protocol) ===
Created: Mar 25 14:08:09 2026 <-- SAME creation time
Modified: Mar 25 14:08:39 2026 <-- only modified time updated (expected)
Inode: 1365599439 <-- SAME inode (same file)MarkdownCreation time and inode are preserved. Ownership, permissions, ACLs, xattrs — all intact.
Setup for Claude Code
Claude Code reads project instructions from a file called CLAUDE.md. To enable this protocol, add the following block to your CLAUDE.md (project-level) or ~/.claude/CLAUDE.md (global, applies to all projects):
## File Metadata Preservation
**IMPORTANT: When editing any file where I specify "preserve metadata", you MUST follow this protocol instead of writing/overwriting files directly.**
When editing files, preserve as much metadata as possible (ownership, permissions, ACLs, xattrs, SELinux context, creation time, inode) by keeping the original inode. Do NOT write a new file in place. **Before starting, check if the file is a symlink (`ls -l`). If it is, follow the symlink and apply this protocol to the actual target file, not the symlink itself.** Then follow this sequence: `TAG=$(date +%Y%m%d_%H%M%S)` to generate a timestamp tag → rename the original using the pattern `file_original-${TAG}.ext` for files with extensions or `file_original-${TAG}` for extensionless files (e.g. `Makefile_original-20260325_140809`, `.bashrc_original-20260325_140809`) → `cp BACKUP ORIGINAL_NAME` (disposable working copy) → make edits to the working copy → **before proceeding, ask the user if they are done with their changes and whether to update the original file or discard changes** → if yes: `cat WORKING_COPY > BACKUP` (writes new content into original inode, `>` does not create a new file) → `rm WORKING_COPY` to remove the disposable copy → `mv BACKUP ORIGINAL_NAME` (file now has new content with all original metadata intact). If the user says discard: `rm WORKING_COPY` → `mv BACKUP ORIGINAL_NAME` (original file restored unchanged).MarkdownThat’s it. One paragraph.
How It Works, Step by Step
Given a file config.yaml that you want to edit:
| STEP | COMMAND | WHAT HAPPENS |
|---|---|---|
| — | ls -l config.yaml | Check if it’s a symlink. If yes, resolve it and apply the protocol to the target file instead. |
| 0 | TAG=$(date +%Y%m%d_%H%M%S) | Generate timestamp tag (e.g. 20260325_140809). |
| 1 | mv config.yaml config_original-${TAG}.yaml | Rename. Same inode, all metadata stays on the backup. For extensionless files (e.g. Makefile), use Makefile_original-${TAG}. |
| 2 | cp config_original-${TAG}.yaml config.yaml | New disposable copy to work on. |
| 3 | (edit config.yaml) | AI tool edits the working copy freely. |
| 4 | (ask user) | “Are you done? Update original or discard?” |
| 5a | cat config.yaml > config_original-${TAG}.yaml | Writes new content into the original inode. > opens the existing file for writing — no new file created. |
| 5b | rm config.yaml | Delete the disposable copy. |
| 5c | mv config_original-${TAG}.yaml config.yaml | Rename back. Done. |
If the user says discard at step 4, skip 5a and just do 5b + 5c — the original is restored untouched.
The timestamp tag avoids collisions if a previous session was interrupted and left a backup behind.
How to Invoke It
You control the scope with the bold directive at the top of the block. Some options:
- Always on:
"When editing any file, you MUST follow this protocol..." - Remote only:
"When editing any file via SSH..." - On demand:
"When I say 'preserve metadata'..."— then just mention “preserve metadata” in your request when you want it.
Example Session
Here’s what it looks like in practice inside Claude Code:
You: edit /etc/myapp/config.yaml, change the port to 8080. preserve metadata.
Claude: Starting metadata preservation protocol.
Symlink check:
> ls -l /etc/myapp/config.yaml
-rw-r--r-- 1 root root 245 Mar 20 10:00 /etc/myapp/config.yaml
Not a symlink. Proceeding.
Step 0: Generate timestamp tag.
> TAG=20260325_140809
Step 1: Rename original to preserve inode + metadata.
> mv /etc/myapp/config.yaml /etc/myapp/config_original-20260325_140809.yaml
Step 2: Create a disposable working copy.
> cp /etc/myapp/config_original-20260325_140809.yaml /etc/myapp/config.yaml
Step 3: Making edits.
> (edits port value to 8080 in config.yaml)
Step 4: Are you done with the changes? Should I update the original
file, or discard?
You: looks good, update it.
Claude:
> cat /etc/myapp/config.yaml > /etc/myapp/config_original-20260325_140809.yaml
> rm /etc/myapp/config.yaml
> mv /etc/myapp/config_original-20260325_140809.yaml /etc/myapp/config.yamlMarkdown
Done. config.yaml updated with all original metadata intact.
Why Not Just Use cp -p or install?
Tools like cp -p try to preserve metadata when copying, but they can only preserve what your current user has permission to set. If the file is owned by root or another user, cp -p will silently fail to restore ownership. The protocol above never changes ownership in the first place — the original inode is never replaced, so there’s nothing to restore.
Wrapping Up
One paragraph in your CLAUDE.md is all it takes. The AI tool does the extra steps, you get a confirmation prompt before anything is committed, and your file metadata survives every edit. Drop it in and forget about it.
Leave a Reply