●2.1.300 — Adds virtualText for Mod prompt decorations; background commands still running 1.5 seconds after exit are now force-killed●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 46 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●CACHE — Prompt cache reads on Sonnet 5.5 dropped from $0.20 to $0.10 per million tokens (Oct 7)●NEW — I Put a Ten-Line Promise File at the To…●2.1.300 — Adds virtualText for Mod prompt decorations; background commands still running 1.5 seconds after exit are now force-killed●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 46 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●CACHE — Prompt cache reads on Sonnet 5.5 dropped from $0.20 to $0.10 per million tokens (Oct 7)●NEW — I Put a Ten-Line Promise File at the To…
Visible in the Listing, Zero Hits by Name: Counting Japanese Filenames With Split Dakuten
In a folder I gave Cowork, Japanese filenames were missing from name searches. Counting about 19,000 entries turned up 17 names with decomposed dakuten. Here is how I counted and fixed it.
I was looking for a file called ページ.csv in a folder I'd handed to Cowork. It was right there in the listing, identical to what I typed. Yet find with the same name returned zero results.
The short version: the names looked the same but were different sequences of characters underneath. Below are the numbers from my own folder, how I counted, and how I'd fix it.
What was different
Unicode can write "ペ" as one character (U+30DA, NFC), or as "ヘ" followed by a combining semi-voiced mark (U+309A, NFD). On screen they're indistinguishable.
Same visible name, two searches:
Name used in the search
find -name hits
"ページ.csv" typed on the keyboard (NFC)
0
The same name converted to NFD
4
The lengths differ too: 7 characters in NFC, 9 in NFD. The two invisible extras are the dakuten and handakuten.
The audit
I counted the whole folder (excluding node_modules and .git) read-only:
Item
Count
Files and folders in total
19,024
Names containing non-ASCII characters
43
Of those, NFD (not equal to their NFC form)
17
Distinct NFD names
5
About 40% of the Japanese names were NFD. What surprised me is that 17 hits were only 5 distinct names, repeated across folders. One was a Dropbox "conflict copy" name.
I haven't worked out why they ended up decomposed. Japanese names saved on a Mac sometimes stay in decomposed form, but I haven't confirmed that all 17 share one cause.
✦
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 count how many files in your own folder look present but won't match a name search, using a read-only script
✦You can add one line of normalization to any Japanese-filename lookup and prevent silent misses
✦You can build a rename pass with a dry run and a collision check, so a bulk fix never overwrites a file
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.
#!/usr/bin/env python3"""List filenames that differ from their NFC form. Read-only."""import os, sys, unicodedataroot = sys.argv[1] if len(sys.argv) > 1 else "."skip = {"node_modules", ".git"} # huge, and not where Japanese names livetotal = non_ascii = decomposed = 0for cur, dirs, files in os.walk(root): dirs[:] = [d for d in dirs if d not in skip] # prune before descending for name in dirs + files: total += 1 if name.isascii(): continue # plain ASCII names are fine non_ascii += 1 if unicodedata.normalize("NFC", name) != name: decomposed += 1 print("NFD:", os.path.join(cur, name))print(f"total={total} non_ascii={non_ascii} decomposed={decomposed}")
Assigning to dirs[:] is what tells os.walk not to descend. An if inside the loop would still walk all of node_modules.
Step 2: normalize on the search side
You don't have to rename anything to stop missing files:
import os, unicodedatadef find_by_name(root, wanted): """Match names regardless of NFC/NFD.""" key = unicodedata.normalize("NFC", wanted) hits = [] for cur, dirs, files in os.walk(root): for name in files: # normalize the on-disk name before comparing if unicodedata.normalize("NFC", name) == key: hits.append(os.path.join(cur, name)) return hits
This found all four files with the NFC name. Normalize both sides. Fixing only one still misses.
Step 3: if you rename, dry-run first
#!/usr/bin/env python3"""Rename NFD names to NFC. Dry run by default; --apply to execute."""import os, sys, unicodedataroot, apply = sys.argv[1], "--apply" in sys.argv# topdown=False: fix children first so a parent rename can't invalidate pathsfor cur, dirs, files in os.walk(root, topdown=False): for name in files + dirs: nfc = unicodedata.normalize("NFC", name) if nfc == name: continue src, dst = os.path.join(cur, name), os.path.join(cur, nfc) if os.path.exists(dst): # never overwrite an existing NFC file print("SKIP (exists):", dst) continue print("RENAME" if apply else "DRY-RUN", ascii(name), "->", nfc) if apply: os.rename(src, dst)
I tried it in a scratch folder. Two NFD files showed up in the dry run, and after --apply there were zero NFD names. With an NFC file of the same name already present, a second run printed SKIP (exists) and overwrote nothing.
Fix the search, or fix the names?
Working files in a synced folder end up mixed: some exported, some saved from other apps. For these 17 I chose to leave the names alone and normalize on the search side (Step 2), for three reasons.
NFD names are under 0.1% of the folder (17 of 19,024). Worrying about sync and references cost more than the misses did
There were only 5 distinct NFD names, and normalizing at lookup gave the same result for all of them
A rename is hard to undo. A lookup function can simply be deleted if I get it wrong
If an automated job types the same names repeatedly, renaming may be quicker, and at thousands of files I'd change my answer.
When you ask Cowork to find something
The same trap applies when Cowork searches for you. A "not found" doesn't tell you whether the file is missing or only spelled in a different form.
When a Japanese filename comes back with zero hits, I run one line to check:
# Re-run the search with the name converted to NFD (example: ページ.csv)find . -name "$(python3 -c "import unicodedata;print(unicodedata.normalize('NFD','ページ.csv'))")"
If this returns results, the file exists and only its form differs. Putting this check before accepting "zero" saves you from recreating a file you already have.
Pitfalls
Renaming in a synced folder. I haven't tested how Dropbox reacts. On the real folder I only changed the search side (Step 2)
Things that reference the old name. Configs or scripts that spell the NFD name will break after a rename. Search for references first
Cowork's own search tooling. I tested find, ls with grep, and Python's os.path.exists. I did not test whether Cowork's internal search behaves the same under identical conditions
The line I draw
What I can see and what I can search are not the same thing. For any Japanese filename lookup, I normalize first instead of trusting how it looks.
As an indie developer who keeps working files in a synced folder, I'd start with Step 1 on whichever folder has the most Japanese names, read-only. Zero is the best possible result.
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.