ANTIGRAVITY LABJP
Articles/Agents & Manager
Agents & Manager/2026-07-15Advanced

The File Is Right There in ls, and Your Agent Still Can't Open It

The agent says the file does not exist. Your terminal says it does. After three days of blaming cloud sync, the answer turned out to be that one voiced consonant mark was never a single character. Detection script and a three-layer gate included.

antigravity434agents127unicodetroubleshooting107workflow50

Premium Article

Here is the line that was sitting in an overnight run log:

FileNotFoundError: [Errno 2] No such file or directory:
  'content/reference/壁紙_配色ガイド.md'

Run ls content/reference/ on the same machine and the file is right there, calm as anything. Character by character, it is the same name. The agent insisted otherwise.

Copy the name and paste it: it opens. Let the agent construct it: it doesn't. That asymmetry was the part that bothered me.

I Spent Three Days Blaming the Wrong Thing

My first theory was wrong. It seems worth admitting that up front.

My working folder sits under cloud sync, and I had been burned before by grabbing a placeholder and reading an empty file. So I assumed a repeat, adjusted exclusion rules, forced a full local download, watched the logs, and got the same error.

Day two, I suspected permissions. Owner was fine. Mode was fine. Nothing to see.

Day three, out of ideas, I looked at the bytes.

$ ls content/reference/ | grep 壁紙 | xxd | head -3
00000000: e5a3 81e7 b499 5fe9 858d e889 b2e3 8299  ....._........
00000010: e382 ace3 82a4 e383 895f 2e6d 6400       ........._.md.

There it is: e3 82 99, a combining voiced sound mark. The character after 配 in that filename was not as a single code point. It was followed by a standalone — two characters wearing one glyph.

The agent was looking for a name containing (e3 82 ac). The filesystem held a name containing + . To a human eye, identical. As byte strings, unrelated.

No such file or directory was the correct answer. The agent had been right the whole time.

Why Agents Trip on This and People Don't

This is the crux. As long as humans are the ones touching the repo, this problem stays almost completely invisible.

The reason is mundane: people either copy-paste filenames or hit tab-complete. Both hand you the exact bytes the filesystem returned, so you ask for the file using the same byte string that was written.

Agents don't do that. An agent reconstructs the path from prose in its context. If a design note says "see 壁紙_配色ガイド.md," the model reads that string and rebuilds it as a path. And what a language model emits is, essentially without exception, precomposed (NFC).

macOS, for historical reasons, hands back decomposed (NFD) names. Create a folder in Finder, type a Japanese name, and the voiced mark lands as its own separate character.

So the picture looks like this:

Path came fromForm on diskBytes for "ガ"
Named in macOS FinderNFD (decomposed)e3 82 ab + e3 82 99
Generated by a model from contextNFC (precomposed)e3 82 ac
mkdir on a Linux VMNFC (as handed in)e3 82 ac
Git indexWhatever was writtenEither

Git does not arbitrate here. core.precomposeunicode will normalize what comes in through macOS going forward, but it will not retroactively fix names already committed in decomposed form. My repos still carried NFD files from years before I learned that setting existed.

Then comes the genuinely bad part. When an agent decides a file is missing, it helpfully creates it.

content/reference/壁紙_配色ガイド.md   ← present since 2023 (NFD)
content/reference/壁紙_配色ガイド.md   ← created last night by an agent (NFC)

Two identical-looking rows in ls. Different contents. One holds years of accumulated notes; the other is nearly empty and was born at 3am. Which one gets read depends entirely on how the path was assembled that time. I've started calling these ghost duplicates, and I treat them as an incident rather than a curiosity.

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
A 60-line scanner that separates 'looks identical' from 'is identical' by comparing bytes instead of glyphs
Three independent failure axes — normalization, case folding, invisible characters — closed by three layers that each do one job
Six weeks of unattended runs: 14 drifted names, 3 pairs of ghost duplicates, and where tolerant resolution stops being a good idea
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.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity 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 →

Related Articles

Agents & Manager2026-06-18
Three Boundaries I Draw Before Handing Work to an Antigravity 2.0 Agent
What to hand a background agent, and what to keep in your own hands. The three boundaries I actually drew while running solo-dev automation in parallel, and how to encode them so the lines hold.
Agents & Manager2026-05-30
When the Antigravity Agent Says 'command not found' for node or python: Causes and Fixes
It works in your own terminal, but the Antigravity agent hits command not found. Starting from how PATH inheritance works, here are concrete fixes for nvm, pyenv, Homebrew, and WSL setups—plus how to confirm the fix actually took.
Agents & Manager2026-05-26
Why Antigravity's Browser Sub-Agent Reads SPAs as Empty Pages — and Three Wait Strategies That Stuck for Me
When you hand an SPA dashboard to Antigravity's Browser Sub-Agent, get_page_text often returns before the real content is rendered, and the agent reports an empty page. Here is how I diagnose the symptom and the three wait strategies that have stabilized my routine.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →