Skip to main content
Steve McGarvey
ESSAY

An AI Agent Stripped 584 Links From My Notes. Here Is the Post-Mortem.

An AI assistant deleted 584 wikilinks across 367 files in my Obsidian vault in a single morning. Everything came back. The reason the failure caught me three times before I noticed it is the part worth writing down.

July 27, 2026 · 8 min read by Steve McGarvey

Summary

  • An AI assistant turned a 'fix seven files' request into a vault-wide script, deleting 584 wikilinks across 367 files before reporting what it had done.
  • The same failure mode, acting or asserting without checking first, showed up three times that morning at three different scales, and only the third time was expensive.
  • Recovery worked because of macOS's local APFS snapshots, not any AI safeguard; the real fix going forward is a hard gate requiring a dry run and diff before any multi-file operation runs.

Date of incident: July 27, 2026 Time to detect: immediate, the agent reported it Time to full recovery: about 90 minutes Data permanently lost: one file, 16 links

I spent a morning doing Bible study with an AI assistant in my Obsidian vault. By lunch it had modified 367 files I never asked it to touch and stripped 584 wikilinks out of them.

Everything came back. But the way it happened is worth writing down, because the interesting part is not “AI made a mistake.” The interesting part is that the same failure showed up three times in one session, at three different scales, and I only caught it the third time.

What happened

The morning started well. We worked through Hebrews 3:14, built out a study thread, and created some notes. Along the way the assistant created about 48 links to notes that did not exist yet. In Obsidian these show as red links, and clicking one creates an empty file.

I clicked one. I got a blank page at the root of my vault.

I told the assistant that a link should lead to something, and that this needed to be a standing rule, not a one-off fix. It wrote the rule into my instruction files. Good.

Then I said “let’s resolve all of it so we’re tighter moving forward,” meaning clean up the 48 links from that morning.

It wrote a script. The script walked my entire vault, found every link whose target file did not exist, and deleted the brackets.

367 files changed. 584 unique link targets removed. It reported this to me after it had written the changes.

What it destroyed

Not just my Bible study links.

  • Hundreds of people links. One team roster lost 122. Another lost 110. Bare [[Name]] links, which is exactly how a people index is supposed to work in Obsidian.
  • Template placeholders. My Templater files contained tokens like a wikilinked tp.file.title call and a literal Person Name stand-in. Those are never supposed to resolve. They are the template. Templates broken.
  • Image embeds. The pattern ![[image.jpg]] contains [[image.jpg]]. The regex matched the inside and left !image.jpg behind. Images stopped rendering.
  • Daily note indexing. Every daily note starts with tags:: [[+Daily Notes]]. That is how they group. Gone.
  • The instruction file it had written an hour earlier to prevent this class of problem. Its own examples got stripped.

The three root causes

1. The literal bug: scope

I asked it to fix seven files. The first line of its script was the equivalent of “walk the entire vault.”

That is the whole bug. Everything downstream follows from it.

2. Write-then-report

The script made 367 changes, then printed a summary. If it had printed the summary first and waited, I would have seen “367 files” and stopped it in one second. So would the assistant, honestly. Nobody involved wanted that outcome.

There was no gate between intent and execution.

3. Regex over structured text

A wikilink looks simple. It is not. It sits inside embeds, code fences, template tokens, YAML frontmatter, and tables where the pipe character is escaped. A regex that matches [[...]] will match all of those and understand none of them.

If you are doing surgery on markup, a regex is a blunt tool.

The thing I actually learned

Here is what makes this worth publishing.

The same failure mode appeared three times that morning, and only the third one was expensive.

  1. Early on, the assistant flagged a problem in my daily notes and told me it needed a decision. I had already fixed it the day before. It had not read the files. It reported from inference.

  2. In the middle, it ran the vault-wide script without checking how many files were in scope.

  3. Afterward, it told me there was no way to undo the damage. No git, no trash, no snapshots. It had checked the filesystem. It had not read the Obsidian config file sitting right there, which showed that both Sync and File Recovery were enabled.

Three instances of the same thing: acting or asserting without looking first. Cheap the first time, catastrophic the second, and nearly cost me a full restore the third.

That pattern is worth watching for in any agent. It is not a knowledge problem. The information was available every time. It is a discipline problem, and discipline is what you have to build into the process rather than hope for in the model.

Why writing a rule did not help

An hour before the incident, we wrote a careful instruction file about when to create links. The assistant then violated the entire spirit of that file through a completely different mechanism.

This is the part I keep thinking about.

Rules help with judgment errors. “Should I link this?” is a judgment call, and a written rule shapes it.

Rules do nothing for blast radius errors. “How many files does this touch?” is not a judgment call. It is a fact that either gets checked or does not. Writing “be careful” in a config file does not make anything get checked.

Those two failure types need different controls, and conflating them is comfortable and useless.

What actually saved me

APFS local snapshots. This is the most useful thing in this whole post.

macOS keeps local Time Machine snapshots on your internal drive, hourly, for roughly 24 hours, even when no backup drive is connected. Most people have no idea these exist. One command shows them:

tmutil listlocalsnapshots /

I had one from 06:49 that morning, before the assistant touched anything. That snapshot is what turned a catastrophe into an inconvenience.

I also learned something I had assumed wrong, and so does nearly everyone I have talked to since:

Obsidian Sync cannot restore your whole vault to a point in time. Version history is per file. There is a bulk-restore checkbox, but it only appears in the “Deleted files” view. My files were modified, not deleted, so it did not apply. Restoring 367 files through Sync would have meant 367 trips through the version history dialog.

Know this before you need it, not during.

The recovery

For anyone who ends up here from a search, this is the shape of it.

  1. Confirm a snapshot exists that predates the damage, using tmutil listlocalsnapshots /
  2. Confirm the folder is not excluded: tmutil isexcluded ~/path/to/vault
  3. Restore through the Time Machine app and choose Keep Both, never Replace. Replace destroys the work you did after the snapshot.
  4. That leaves a clean copy beside the damaged one
  5. Compare the two and restore only the files that differ

That last step is the important one. Blanket-restoring would have wiped a morning of legitimate work. Comparing let me put back 359 damaged files while keeping 29 new ones and all of my own annotations.

Final tally: 359 files restored by exact file copy, zero failures, zero remaining differences from the clean backup. One file could not be recovered because a different process had created it after the snapshot, and it lost 16 links.

What I changed

Version control in the vault. Not because it prevents mistakes. Because it makes them cost ten seconds instead of ninety minutes. This should have been in place before I ever gave an agent write access, and I have no good excuse.

A hard gate on bulk operations. Anything touching more than one file runs read-only first, prints the affected files and a sample of the diff, and waits. This is not advice. It is a precondition.

Scope binding. Operations take an explicit list of files. No walking the directory tree. If the thing cannot name what it is about to change, it does not run.

Targeted edits over scripts for anything small. A find-and-replace tool that changes one specific place and fails loudly when the text does not match is safer than a regex that succeeds silently everywhere.

If you are letting an AI agent touch your files

Seven things, in the order I would do them.

  1. Turn on version control first. Before the agent gets write access. Not after.
  2. Learn your recovery paths while nothing is wrong. Write the runbook cold. Under pressure you will guess, and you will guess badly.
  3. Check whether local snapshots are running. On a Mac they probably are, and almost nobody knows.
  4. Do not assume your sync tool has a bulk undo. Go look. Mine did not.
  5. Require a dry run and a diff for anything touching more than one file. No exceptions, including for the confident ones.
  6. Watch for the pattern, not the incident. One mistake is noise. The same mistake at three scales in one morning is a process gap.
  7. The thing that made the mess is not the best judge of how to clean it up. When the assistant offered to reconstruct 584 links from memory, the right answer was no. It would have produced errors I would be finding in ones and twos for months.

A note on tone

I am not writing this to dunk on AI assistants. The same session produced genuinely good work: careful Greek analysis, a solid study framework, and it talked me out of using a weak argument that I would have been embarrassed to lean on later.

It also had the sense to stop, say plainly what it had done, quantify it, and recommend against its own proposed fix. That mattered.

The lesson is not “do not use these tools.” The lesson is that a tool with write access to seventeen hundred files needs a gate between intent and execution, and that gate is your job to build. Nobody hands it to you.

Set up version control. Then go have fun.