◉CLAUDE LABJP
●2.1.312 — Fixes Claude overwriting very large CLAUDE.md files and skills going missing after a Bash cd●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 32 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…●2.1.312 — Fixes Claude overwriting very large CLAUDE.md files and skills going missing after a Bash cd●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 32 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…
Articles/Claude Code
⟐ Claude Code/2026-10-29Intermediate

When Claude Overwrites a Huge CLAUDE.md: Find the Missing Headings, Restore Them, and Block the Next One

Claude Code 2.1.312 fixed a bug where Claude overwrote a very large CLAUDE.md after seeing only its start. Here is how to spot lost headings on older versions, restore with git, and block full rewrites with a Write hook.

Claude Code278CLAUDE.md8Troubleshooting25git19Hooks7rules

One line in the Claude Code 2.1.312 changelog made me stop scrolling: Claude had been overwriting very large CLAUDE.md files, rules files and @-included files after seeing only the start of them. I could not say, off the top of my head, how many characters my own CLAUDE.md had.

As an indie developer, I keep the operating rules for my apps and websites in one CLAUDE.md, and I've watched it grow past what I can hold in my head. Then you ask Claude to "tidy it up." It is a convenient request, but if the reader saw only the first half, the second half may be treated as if it never existed.

Here is the short version first: from 2.1.312 on, Write asks Claude to read the whole file or edit it in place. Machines that have not been updated, or a teammate's checkout, can still hit the old behavior. So below I write down how to notice it, how to restore, and how to stop the next one.

A caveat: the Claude Code behavior comes from the changelog wording, and I did not try to reproduce the bug itself. What I did test is the receiving side. I built a 12-chapter, 8,552-character CLAUDE.md for testing, simulated the "back half is gone" state, and ran the scripts below against it.

Why only the back half disappears

The mechanism is simple. Large instruction files can be handed to Claude as just their first portion. I wrote about the character limit on subfolder CLAUDE.md files in When Only the Back Half of a Subfolder CLAUDE.md Is Ignored, Count Characters, Not Bytes.

If Claude sees only the start and decides to "rewrite this file" with Write, the unseen back half is simply not in the new content. It is a replacement rather than an edit, so nothing warns you that something vanished.

The changelog names CLAUDE.md, rules files, and @-included files in subdirectories. I would assume the same symptom can show up elsewhere for other reasons.

Step 1: Check your version and the size of your instruction files

Start with the version. If it is older than 2.1.312, a safety net is worth having.

claude --version

Then list instruction files, largest first. For Japanese text, wc -c counts bytes and overstates the length two to three times, so use wc -m with a fixed locale.

# Problem solved: know which instruction files are big enough to be read only partially
find . -type f \( -name CLAUDE.md -o -path '*/.claude/rules/*.md' \) \
  -not -path '*/node_modules/*' | while read -r f; do
  printf '%7s chars  %s\n' "$(LC_ALL=C.UTF-8 wc -m < "$f")" "$f"
done | sort -rn | head

On my test file the output looked like this (the same file is 22,552 with wc -c):

   8552 chars  ./sub/CLAUDE.md
   8552 chars  ./sub/.claude/rules/a.md
   8552 chars  ./CLAUDE.md

LC_ALL=C.UTF-8 matters because under the C locale wc -m also counts bytes.

Step 2: Check for loss by comparing headings

When something feels off, look at the shape of the git diff first. A file whose back half was cut by a replacement shows almost no additions and a large number of deletions.

git diff --numstat
# 1	138	CLAUDE.md   <- 1 line added, 138 deleted

One line added, 138 deleted: that shape says "cut off," not "tidied." Before reading any content, let a script list the headings that disappeared.

#!/usr/bin/env bash
# lost_headings.sh - print headings present in a base revision but missing now
# Usage: lost_headings.sh CLAUDE.md [base revision, default HEAD]
set -euo pipefail
f="${1:?file}"; rev="${2:-HEAD}"
old="$(mktemp)"; trap 'rm -f "$old"' EXIT
git show "${rev}:${f}" > "$old"
echo "lines: $(wc -l < "$old") -> $(wc -l < "$f")"
echo "chars: $(LC_ALL=C.UTF-8 wc -m < "$old") -> $(LC_ALL=C.UTF-8 wc -m < "$f")"
echo "--- missing headings ---"
diff <(grep -E '^#{1,4} ' "$old") <(grep -E '^#{1,4} ' "$f") | grep '^<' | sed 's/^< //' || true

Run against my simulated cut-off state:

lines: 206 -> 68
chars: 8552 -> 2850
--- missing headings ---
## 第5章 ルール5
## 第6章 ルール6
(...omitted...)
## 第12章 ルール12

Headings are a good yardstick because they separate rewording from loss. If Claude only polished the wording, the headings survive. If whole headings are gone, the text was not polished; it was dropped.

Step 3: Restore

If the diff is only the cut-off, restoring the committed version is the shortest path.

git restore CLAUDE.md

If your own uncommitted edits were in the same file, git restore will discard them too. In that case, pull the old version out under another name and paste back only the missing chapters.

git show HEAD:CLAUDE.md > /tmp/CLAUDE.old.md
# open /tmp/CLAUDE.old.md and copy the missing chapters back into the current file

For a folder not under git, the version history of Dropbox or iCloud is the last resort. But digging through history takes time. Put instruction files in git—that is the one thing worth deciding up front.

Step 4: Stop it from happening again

Three measures, in order of payoff.

First, commit before you ask. A diff exists only if there is a baseline, and one commit before "please tidy this up" is the cheapest insurance.

Second, change how you ask. Add one line: "Fix only this section with Edit; do not rewrite the whole file." Edit replaces in place and does not touch what Claude has not read.

Third, a hook on Write. When a Write would replace an instruction file in full and drop headings or shrink it sharply, the hook sends it back to Claude.

#!/usr/bin/env python3
# .claude/hooks/guard_instruction_rewrite.py
# Problem solved: stop a full-file Write from dropping the back half of an instruction file
import json, os, re, sys
 
WATCHED = re.compile(r"(^|/)(CLAUDE(\.local)?\.md|\.claude/rules/.+\.md)$")
MIN_CHARS = 6000      # smaller files pass through
SHRINK_LIMIT = 0.7    # block if the new content is under 70% of the old
 
def headings(text):
    return [l.strip() for l in text.splitlines() if re.match(r"#{1,4} ", l)]
 
def main():
    # read stdin as bytes and force UTF-8 (avoids cp932 problems on Windows)
    data = json.loads(sys.stdin.buffer.read().decode("utf-8"))
    if data.get("tool_name") != "Write":
        return 0
    inp = data.get("tool_input", {})
    path = inp.get("file_path", "")
    if not WATCHED.search(path.replace("\\", "/")) or not os.path.isfile(path):
        return 0
    old = open(path, encoding="utf-8", errors="replace").read()
    new = inp.get("content", "")
    if len(old) < MIN_CHARS:
        return 0
    lost = [h for h in headings(old) if h not in set(headings(new))]
    ratio = len(new) / len(old)
    if lost or ratio < SHRINK_LIMIT:
        print(f"[guard] {path}: {len(old)} chars -> {len(new)} chars "
              f"({ratio:.0%}), {len(lost)} headings would disappear. "
              "Read the whole file again, or use Edit on the section you need.",
              file=sys.stderr)
        return 2   # exit code 2 blocks the write and returns stderr to Claude
    return 0
 
if __name__ == "__main__":
    sys.exit(main())

Register it in .claude/settings.json, for Write only.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard_instruction_rewrite.py\""
          }
        ]
      }
    ]
  }
}

I fed it mock requests on standard input. Results:

Content being writtenExit codeWhy
Only the first third2 (blocked)Shrinks to 33%, 8 headings vanish
Identical content0 (allowed)No change
One section appended0 (allowed)No heading lost, no shrinkage
One heading line deleted (chapter 5)2 (blocked)Still 100% in size, but a heading is gone
A file other than CLAUDE.md0 (allowed)Out of scope

Blocking on a single deleted heading is deliberate. When I really do want to drop a chapter, I either disable the hook briefly or delete it with Edit.

Pitfalls

The hook guards Write only. A Bash redirect such as cat > CLAUDE.md has a different matcher and walks straight past it. Closing that gap means combining the hook with deny rules.

The other pitfall is a hook that quietly does nothing. I wrote up a hook for Japanese paths that never blocked anything in The Hook That Was Meant to Guard My Japanese Folder Never Blocked Anything. After adding a hook, always trigger it once with a mock input. If you break the settings JSON along the way, When a Broken settings.json Stops Claude Code From Starting covers recovery.

MIN_CHARS and SHRINK_LIMIT are tuned to my test file. Check your own file sizes with Step 1 and adjust.

One thing to do next

A document you have not fully read gets edited only in parts. I want that line drawn both in how I ask and in the tools I put in the way.

Run the find from Step 1 once, check the character count of your largest instruction file, and commit that file. The hook can wait until after that. It is the order I followed in my test setup as well.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⟐ Claude Code2026-10-28
Back From Checkout, the Button Stayed on Processing: Why I Fixed It With pageshow, and the 3 Files My Audit Still Found
When you press Back from Stripe or another external page, a checkout button can stay stuck on Processing. I reproduce the bfcache cause in a real browser, compare pageshow with no-store, and add a repo audit script and a CLAUDE.md rule.
⟐ Claude Code2026-10-23
When a Nightly claude -p Says MCP "Needs Authentication": Check Mac Sleep and the Keychain First
If an unattended claude -p run leaves "needs authentication" or MCP timeouts in the morning log, check whether the Mac slept or the login keychain was locked. What v2.1.307 changed, and what it still leaves to you.
⟐ Claude Code2026-10-20
You Stepped Away and Came Back to a Summary — Spotting Idle Compaction and Keeping the Details You Can't Lose
Since v2.1.299, Claude Code compacts a long conversation after about 54 idle minutes. Here is how to tell it happened, how to tune or switch it off, and how to move the assumptions you can't afford to lose into CLAUDE.md.