●CLI — Claude Code v2.1.260 shipped on September 3. The new /diff panel opens beside the conversation and shows uncommitted changes as Claude edits them●FIX — Fullscreen no longer blanks the transcript after a terminal resize, and a diff containing one very long line no longer grinds rendering to a halt; such lines are now truncated with a marker●MCP — The 2026-07-28 spec moves the protocol to a stateless core, so servers can run on serverless and edge infrastructure. MCP Apps and Tasks now ship under a versioned extensions framework●SCALE — MCP passed 400M monthly SDK downloads, a fourfold increase this year, and the Claude connectors directory now lists over 950 MCP servers●COMMERCE — On September 2 Anthropic published both an announcement and a design guide for commerce agents. The heart of it is where to draw the line on autonomy when payments and inventory are involved●MEMORY — Since August 25, Claude's memory carries across surfaces and you decide what goes into it. Being able to choose what it should not remember is the part that matters most in daily work●CLI — Claude Code v2.1.260 shipped on September 3. The new /diff panel opens beside the conversation and shows uncommitted changes as Claude edits them●FIX — Fullscreen no longer blanks the transcript after a terminal resize, and a diff containing one very long line no longer grinds rendering to a halt; such lines are now truncated with a marker●MCP — The 2026-07-28 spec moves the protocol to a stateless core, so servers can run on serverless and edge infrastructure. MCP Apps and Tasks now ship under a versioned extensions framework●SCALE — MCP passed 400M monthly SDK downloads, a fourfold increase this year, and the Claude connectors directory now lists over 950 MCP servers●COMMERCE — On September 2 Anthropic published both an announcement and a design guide for commerce agents. The heart of it is where to draw the line on autonomy when payments and inventory are involved●MEMORY — Since August 25, Claude's memory carries across surfaces and you decide what goes into it. Being able to choose what it should not remember is the part that matters most in daily work
I verify what my Cowork memory claims instead of trusting its timestamp
Persistent memory keeps asserting whatever was true the day you wrote it. After an unattended job quietly read an empty folder for months, I stopped judging memory by its modification date and started attaching a verification step to every claim that can rot.
On a Friday evening I noticed that one of my asset-collection jobs had finished cleanly with a count of zero.
No error. No warning. The log had the count and the elapsed time, nothing else. When I walked through the same steps by hand, the answer showed up quickly: the folder the job was reading had been emptied months earlier, when I reorganised where those files live.
I remembered moving them. What I had not remembered was that the old location was also written down in persistent memory. The memory file itself had been touched a few weeks earlier, so by its modification date it looked recent enough. The line inside it still pointed at an arrangement from half a year ago. The timestamp was telling me how fresh the file was, not how fresh the claim was.
A memory is an assertion, not a fact. Check that it still holds, once, before handing it over. Since I started running more unattended work as an indie developer, that sentence has sat underneath the whole setup.
The busiest memory files hold the oldest claims
For a long while I worried mostly about what went into memory. Don't write too much, don't mix in secrets, don't duplicate. All reasonable, and all concerned with the moment of writing. The failure happened at the moment of reading, six months later.
Modification dates mislead here in a specific way. You touch a memory file when you want to add something new, and the older lines ride along untouched. So the more actively a memory grows, the more unverified assertions it quietly accumulates inside itself.
A human reading that line would pause — didn't I move that? An unattended run does not pause, because nothing gives it a reason to doubt the premise it was handed. In my experience this class of miss never even registers as a failure. The premise is stale, but the procedure itself completes exactly as written.
My first instinct was to give every memory an expiry — three months, then re-read it. Straightforward enough.
Trying it showed me the flaw. What expires is not the memory but the thing the memory points at. Some claims stay correct for years; others are wrong within days. A uniform expiry throws away the durable ones and keeps the rotten ones.
So I sorted by kind of claim instead. Roughly, mine fall out like this.
Kind of claim
Example
How it rots
Location
Where assets live, canonical paths, output targets
Rots quietly on every reorganisation
Name
Script names, task names, flag names
Rots on rename, invisibly to the reader
Outside world
Model generations, pricing, deprecation dates
Rots without you touching anything
Procedure
Execution order, preconditions, dependencies
Rots the moment the implementation changes
Judgement
Why an approach was rejected, where a line was drawn
Barely rots at all
Preference
Voice, naming habits, phrasings to avoid
Barely rots at all
The top four share a property: each one has a counterpart out in the filesystem or the world, and the presence of that counterpart can be checked by a machine. The bottom two have no counterpart. There is no command that verifies why I chose one design over another.
That line turned out to be exactly the line for "does this need a verification step?" Claims that rot get one. Claims that don't get handed over as they are.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You can stop the quiet failure where a stale memory sends an unattended run to a place that no longer holds anything, before the run even starts
✦You will be able to sort your own memory files into claims that rot and claims that do not, and decide which ones deserve a verification step
✦You get a dependency-free checker and loader you can drop into any unattended pipeline, so failed claims are held back in the open rather than dropped in silence
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The mechanism is small: one verify block in the frontmatter. The body still holds the claim, and beside it sits the thing that ought to pass if the claim still holds.
---name: reference-data-pathdescription: Canonical reference data lives under _reference_data/type: referenceverify: kind: readable # readable, and not empty target: data/_reference_data/keywords.txt---Read reference data from _reference_data/.
---name: wallpaper-batch-orderdescription: Derivation runs before classificationtype: projectverify: kind: grep # is the ordering still in the script? target: scripts/batch.sh pattern: "derive_before_classify"---Swap the order and classification finds nothing.
---name: why-manual-approvaldescription: Irreversible actions stay on my handstype: feedbackverify: kind: none # no counterpart to check---Recording is automatic. Committing is mine.
There are only four kinds: readable (the target opens and has content), grep (a marker is still present in the target), command (exit status is zero), and none (no counterpart). I kept wanting to add more, and I'd rather not: every extra option pushes the writer towards skipping the block entirely. Coarse enough to decide in thirty seconds has served me better than expressive.
grep earns its place more than I expected. Procedural memories go wrong the instant the implementation changes. Leave one marker word in the code, and the memory fails on the day that word disappears.
The checker
No dependencies. Unattended environments are not always the environment you developed in, and there are days when pip install simply doesn't go through. I also capped the frontmatter at two levels of nesting so a regex and a split are enough.
#!/usr/bin/env python3"""Verify what persistent memory claims, right before handing it over."""from __future__ import annotationsimport reimport subprocessimport sysfrom dataclasses import dataclassfrom pathlib import PathFM = re.compile(r"\A---\n(.*?)\n---\n", re.S)def parse_frontmatter(text: str) -> dict: """Dependency-free parser for frontmatter up to two levels deep.""" m = FM.match(text) if not m: return {} data: dict = {} parent = None for raw in m.group(1).splitlines(): if not raw.strip() or raw.lstrip().startswith("#"): continue indent = len(raw) - len(raw.lstrip()) if ":" not in raw: continue key, _, value = raw.strip().partition(":") value = value.strip().strip('"').strip("'") if indent == 0: if value == "": parent = key # nested parent, e.g. verify: data[key] = {} else: parent = None data[key] = value elif parent: data[parent][key] = value return data@dataclassclass Result: name: str status: str # fresh / stale / unverifiable reason: strdef verify(spec: dict, root: Path) -> tuple[str, str]: kind = spec.get("kind", "none") if kind == "none": # Judgements have no counterpart, so there is nothing to check return "unverifiable", "no counterpart (judgement)" if kind == "readable": p = root / spec["target"] if not p.exists(): return "stale", f"missing: {spec['target']}" try: head = p.read_bytes()[:4096] except OSError as e: return "stale", f"unreadable: {e.__class__.__name__}" # A file holding one newline passes both -s and a first-byte read if not head.strip(): return "stale", f"empty content: {spec['target']}" return "fresh", "readable with content" if kind == "grep": p = root / spec["target"] if not p.exists(): return "stale", f"missing: {spec['target']}" pattern = spec.get("pattern", "") if re.search(pattern, p.read_text(errors="replace")): return "fresh", f"matched /{pattern}/" return "stale", f"no match for /{pattern}/" if kind == "command": proc = subprocess.run( spec["target"], shell=True, cwd=root, capture_output=True, timeout=int(spec.get("timeout", 20)), ) if proc.returncode == 0: return "fresh", "exit 0" return "stale", f"exit {proc.returncode}" return "unverifiable", f"unknown kind: {kind}"def scan(memory_dir: Path, root: Path) -> list[Result]: results: list[Result] = [] for path in sorted(memory_dir.glob("*.md")): fm = parse_frontmatter(path.read_text(errors="replace")) if not fm: results.append(Result(path.stem, "unverifiable", "no frontmatter")) continue spec = fm.get("verify") if not isinstance(spec, dict): results.append(Result(fm.get("name", path.stem), "unverifiable", "no verify block")) continue status, reason = verify(spec, root) results.append(Result(fm.get("name", path.stem), status, reason)) return resultsdef main() -> int: memory_dir = Path(sys.argv[1] if len(sys.argv) > 1 else "memory") root = Path(sys.argv[2] if len(sys.argv) > 2 else ".") results = scan(memory_dir, root) mark = {"fresh": "OK ", "stale": "STALE", "unverifiable": "SKIP "} for r in results: print(f"{mark[r.status]} {r.name}: {r.reason}") stale = [r for r in results if r.status == "stale"] print(f"\nfresh={sum(1 for r in results if r.status=='fresh')} " f"stale={len(stale)} " f"unverifiable={sum(1 for r in results if r.status=='unverifiable')}") return 1 if stale else 0if __name__ == "__main__": raise SystemExit(main())
Running it after removing the marker from the script gives this.
$ python3 memcheck.py memory .OK reference-data-path: readable with contentSTALE wallpaper-batch-order: no match for /derive_before_classify/SKIP why-manual-approval: no counterpart (judgement)fresh=1 stale=1 unverifiable=1$ echo $?1
Put the marker back and the same memory returns to OK without being rewritten. Getting to a state where the checker only goes red when memory and implementation have drifted apart was the part I was most pleased with.
Present but empty should never count as fresh
This is where most of my implementation time went.
My first readable passed anything where Path.exists() returned true. Not enough. Running unattended work on top of a synced folder, you meet files whose metadata has arrived while the content has not. They exist. They report their real size. The read still comes back empty. I covered that behaviour in why bash says a file is missing while Finder shows it.
Reading the first byte fixed that case, and still leaked another.
$ printf '\n' > data/_reference_data/keywords.txt # a single newline$ [ -s data/_reference_data/keywords.txt ] && echo "[ -s ] => true (size is 1 byte)"[ -s ] => true (size is 1 byte)
[ -s ] says true. A first-byte read says true. You get exactly this shape when a writer dies partway through, or when a template is generated and never filled. It ran against my expectation — I had assumed emptiness was the easy part of the check, and it turned out to be the leakiest.
The current version reads the first 4096 bytes and requires strip() to leave something behind. Whitespace-only files fall to stale.
Check
Missing
Unmaterialised, empty
Newline only
exists()
Caught
Missed
Missed
[ -s ]
Caught
Caught
Missed
First-byte read
Caught
Caught
Missed
First 4096 bytes + strip
Caught
Caught
Caught
Treating input freshness as a contract in its own right is something I wrote up in a freshness contract for unattended pipelines. This readable check is that contract viewed from the memory side.
Three things that bit me in production
Everything passed locally, and then three problems showed up once it ran unattended. All three were about the environment around the check rather than the check itself.
The first was command latency. One memory verified itself over the network, and on a morning when that connection hung, the verification pass alone ate several minutes. I now set timeout explicitly with a default of 20 seconds. A check should be lighter than the work it guards — if the confirmation is genuinely expensive, it belongs in the job's preprocessing rather than in a memory's verify block.
The second was symlinks. readable follows them, so a dangling link whose target has been deleted can still resolve to something else depending on the environment and pass. For memories where I want the stricter reading, I switch to command and work around it with something like test -f "$(readlink -f ...)".
The third was the most awkward error of the three. When the checker itself raises, the unattended job cannot tell whether verification passed or never ran, and it proceeds either way. Wrapping the scan() call and treating every memory as stale on failure — failing towards distrust — has produced the fewest incidents for me.
try: results = scan(memory_dir, root)except Exception as e: # infrastructure failure means: trust nothing print(f"verifier failed, holding all memories: {e}", file=sys.stderr) results = [Result(p.stem, "stale", "verifier error") for p in sorted(memory_dir.glob("*.md"))]
If you're unsure which way to lean, I'd recommend this one. Cleaning up after a day spent acting on a stale premise took far longer than stopping that morning and fixing the memory.
Hold failed claims in the open
Deciding what to do with a failed claim took some thought.
Silently dropping it is easiest, and it only changes the shape of the accident. Instead of running on a stale premise, the run proceeds with no premise at all. An unattended run doesn't question either one.
What I do now is withhold the body while keeping the name and the reason. Whoever reads it next — me, or Claude in that session — should be able to tell that the topic exists and is currently not trustworthy.
#!/usr/bin/env python3"""Hand over verified memories in full; keep failed ones as headings only."""from pathlib import Pathfrom memcheck import parse_frontmatter, scandef build_context(memory_dir: Path, root: Path) -> str: status = {r.name: r for r in scan(memory_dir, root)} fresh_blocks, held_blocks = [], [] for path in sorted(memory_dir.glob("*.md")): text = path.read_text(errors="replace") fm = parse_frontmatter(text) name = fm.get("name", path.stem) body = text.split("---", 2)[-1].strip() r = status.get(name) if r is None or r.status == "stale": # Not dropped in silence: "this existed, and cannot be trusted now" held_blocks.append(f"- {name}: held ({r.reason if r else 'not evaluated'})") else: fresh_blocks.append(f"## {name}\n{body}") out = "\n\n".join(fresh_blocks) if held_blocks: out += "\n\n## Held memories (bodies withheld)\n" + "\n".join(held_blocks) return outif __name__ == "__main__": print(build_context(Path("memory"), Path(".")))
With the marker removed from the script, the output looks like this.
## reference-data-pathRead reference data from _reference_data/.## why-manual-approvalRecording is automatic. Committing is mine.## Held memories (bodies withheld)- wallpaper-batch-order: held (no match for /derive_before_classify/)
On days when a held line appears, one of two things needs fixing: the memory is out of date, or the implementation changed in a way I didn't intend. Either is fine, as long as I find out before acting on it.
Claims with no counterpart get handed over anyway
Treating kind: none the same as fresh was deliberate.
Treat unverifiable claims as suspect and the first things you lose are the ones that rot least — why an approach was rejected, where I decided to stop. Those are the memories I most want to keep. They survive because they don't rot, and that ordering seems right to me.
What I do watch for is kind: none sitting on a claim that could have been checked. A memory containing a path or a name with none attached is usually a fossil of the day I couldn't be bothered to write the check.
Deciding what goes into memory at all is a separate question from freshness. Counting secrets by content rather than filename before handing a folder over is in a separate write-up. Freshness checks only run on top of what you already decided was safe to keep.
Rolling it out without stalling every job
Doing all of it at once will stall you. The order that worked for me:
Require verify on new memories only, without re-reading the old ones. Starting with an audit of everything you've written tends to end halfway.
Run the checker at the head of unattended jobs, ignoring the exit code. Log the output and nothing else. Do not block yet.
Read a week of those logs. You'll see which kinds of claim actually fail. For me, location claims stood out well ahead of the rest.
Switch to blocking on failure. Only then add the held-memory display to the loader.
Backfill verify while fixing whatever failed. Let the audit be a by-product of real incidents rather than a project.
I once skipped step 2 and went straight to blocking, and spent a morning with every unattended job refusing to start. Observe first, block second. That's the order I use whenever I add a gate of any kind.
One line to check tomorrow
Open your memory files and look for a line containing a path or a filename. There will be one. Add three lines of verify to it and run the checker once. That alone removes one future run that would have read an empty place and reported success.
I still find memories where I forgot to write the check. What has changed is that doubting them is no longer a job for my attention — and my morning review got shorter because of it. If you're running more unattended work than you can personally supervise, I hope some of this is useful. Thank you for reading.
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.