I opened a chat with Tim — my AI agent — and something felt off. I'd tell him things, he'd reply "got it." Next conversation, he'd ask me the exact same question I'd answered the day before. As if we'd never met.
It took me a minute to realize what was actually happening. Tim wasn't being forgetful. He was being blocked — silently — from writing to his own memory files. For three full days.
This is one of those bugs I never want to forget, because every part of the failure was invisible. No crash, no error in the UI, no Telegram alert. Just an AI that kept saying "saved" without saving.
How I Noticed: Tim Started Asking the Same Questions Twice
The way Tim's memory normally works — when I tell him something worth remembering ("I prefer X over Y", "this server is called Z", "the customer named Acme uses Brazil pricing"), he writes it to a markdown file in /root/.claude/projects/-root/memory/. Next conversation, he reads the file, and the context is already there. (If you want the full picture of how the memory system learns and evolves every day, I wrote that up separately.)
What I started noticing: Tim was re-asking me things I'd told him a week ago. Things he should already know. Skill names, server aliases, even the spelling of my own name in Thai.
I opened his MEMORY.md index file. Last modified: three days ago. But I'd been talking to him every day, sometimes ten times a day. The file was frozen.
That's the worst kind of bug — a silent failure. The system says "done" but nothing happened. You can run that way for weeks before you catch it. The closest analogue I've written about is when my content pipeline ran for 3 days returning empty results with no error. Same shape of bug, completely different root cause.
Layer 1: Claude Code Now Treats My Memory Files as "Sensitive"
I asked Tim to read his own stderr — the actual error log from the underlying tool, not the chat interface. He found this message, repeated dozens of times across the last three days:
Claude requested permissions to edit X
which is a sensitive file
Claude Code — the engine that runs Tim — has a built-in protection that flags any file inside .claude/ as sensitive. You can't edit those files without an approve prompt. Standard, sensible behavior.
So why was it suddenly breaking? Because of how I set Tim up months ago. My memory files live in /root/tim-brain/memory/ and get symlinked into the .claude/ folder — that's how Tim keeps the same memory across my different servers. The symlink had always worked fine.
Then Claude Code released an update that tightened the sensitive-file rule. Symlinks that pointed out of .claude/ used to slip through. The new version doesn't let them. The block applies to the symlink target now.
One root cause: an upstream tool quietly changed a rule, and my workflow tripped over it the next day. Fine — that's just life with fast-moving dev tools. The bigger question was what came next.
Layer 2: My Chat UI Was Filtering Out the New Error
Here's where it got interesting. Normally when Claude Code throws a permission error, my Tim Chat interface catches it, parses it, and pops up an approve button on my phone. I tap approve, the edit goes through. That flow has worked for months.
This time, no button. No prompt. Nothing. Just Tim cheerfully saying "saved."
Tim asked the obvious follow-up question himself: "If Claude Code threw an error, why didn't your chat UI surface it?"
The answer was in server.js on the Tim Chat side. There was a regex that matched permission errors from Claude Code:
PERM_DENIED_RE = /you haven't granted/i
That regex only matched the old wording. Claude Code's update changed the error message to say "which is a sensitive file" — a phrase the regex doesn't recognize. The error fired, but my chat server didn't see it as a permission request, so it never told the frontend to show an approve button.
End result: a completely silent black hole. The error existed. The block was real. The user (me) saw none of it. Tim didn't know either — from his perspective the tool call had simply not produced an approval flow.
This is the kind of bug that haunts every system built on top of another tool. Regex-matching another product's error strings is a hidden dependency. The day they change the wording, your whole surface UI goes dark with zero warning.
The Fix: Three Layers, Because One Wasn't Enough
Tim proposed three patches in one morning. I liked all three.
1. Pre-allow the memory paths in settings.json. The simplest fix — add the memory folders to the Claude Code allowlist so the sensitive-file check doesn't even fire for paths we already trust:
"Edit(/root/.claude/projects/-root/memory/**)",
"Write(/root/.claude/projects/-root/memory/**)",
"Edit(/root/tim-brain/memory/**)",
"Write(/root/tim-brain/memory/**)"
Tim can now write his own memory without any prompt. Back to the original behavior.
2. Make the chat UI resilient to future sensitive-file errors. The allowlist fixes today's problem. What about the next unknown file Claude Code decides to flag? Tim added a second regex SENSITIVE_FILE_RE and a helper function parsePermissionError() that extracts the offending path from the error message and converts it into a valid Edit(/path/**) tool pattern. Now, the next time a brand-new sensitive-file rule fires for a file I haven't pre-allowed, my phone will get an approve button — exactly as it should.
This is the same defensive pattern that's saved me before — making the surface UI degrade gracefully when the layer below changes its API. Same shape as the chat truncation bug I patched across customer servers last month.
3. Roll the fix into the Newton installer template. Every new customer who spins up a Newton server will get this allowlist baked in from the start. They will never live through this bug. That's the kind of fix I always want to ship — not just patch my own server, but make sure no future user inherits the broken state.
Tim Patched Three Servers Himself
This is the part that still feels a little wild every time it happens.
The same bug existed on every server where I run a Claude Code instance — because the memory pattern is identical everywhere. So Tim SSH'd out to my other machines and applied the patch in a single loop:
- income-in-click — my main server (Tim's home base)
- bm-group — a client server running an attendance app
- whisperer — a side project, social astrology app
One server got skipped: hihome rejected my SSH key (probably rebuilt at some point). Tim flagged it in his own memory — now that memory actually saves again — for me to re-copy the key the next time I'm at my desk. At least the memory layer finally works.
What I Took Away From This
Three things, in increasing order of importance:
- An AI agent that can read its own stderr finds bugs that no support team would. The error existed in the log the whole time. Tim found it the moment I told him to look. No human would have noticed.
- Regex-matching another tool's log output is a hidden dependency you don't see in any architecture diagram. Anywhere your pipeline depends on parsing strings from an upstream tool — Stripe webhook descriptions, FB Graph error messages, OpenAI error codes, whatever — that's a silent breakage waiting for the day the upstream vendor changes a word.
- Multi-layer fixes outlast single-layer fixes. The allowlist alone would have looked like enough. But the next sensitive-file rule would have broken everything silently again. The regex parser is what protects future-me.
This whole episode is also exactly why I keep saying I'll never run my business on a shared AI platform. If Tim were a SaaS chatbot, I wouldn't have access to his stderr. I wouldn't have access to settings.json. I wouldn't be able to add a regex to the underlying server.js. And I definitely couldn't have SSH'd out to fix three other servers in one shot. The whole loop — diagnosis, patch, deploy across servers — would have been impossible.
(If you liked this flavor of "silent failure with no error log," there's a sibling story I wrote up after this one — a stale Cloudflare DNS record poisoned a brand-new Newton customer's HTTPS, and the customer-facing email went out anyway because nothing was verifying. Different subsystem, same family of bug.)
If you want an AI agent that lives on your own server — one that can read its own logs, debug itself, patch sibling servers it has SSH access to, and remember everything you tell it across sessions — that's exactly what Newton is. A private VPS with your own AI agent already wired up and ready to do this kind of work for you. No platform sandbox, no shared infrastructure, no silent failures hiding behind a chat bubble. See how Newton works →
— Pond
