Summary
A single file whose name contains characters that are illegal on the target
filesystem aborts the entire download loop, permanently blocking sync of
the whole library on that device until the offending record is changed
server-side.
Impact
Filenames are validated by neither the server (/metadata/commit) nor the
client on download. A file created on a permissive filesystem (e.g. Linux/
Android, which allow ", :, |, etc.) is happily stored and listed by the
server. When another device on a stricter filesystem (Windows forbids
< > : " / \ | ? *, trailing dots/spaces, reserved names like CON) tries to
download it, File::create fails and the error propagates all the way up:
check_download_once returns the IO error (syncer.rs, the chunker.save(...)
call in the post-download write loop)
download_loop maps it to SyncError::Unknown("Check download failed: ...")
and returns, killing the loop
On Windows this surfaces to users as:
Check download failed: IO error in file ... OS error 123
(ERROR_INVALID_NAME). Because the same record is re-fetched on every retry,
the loop never makes progress — one bad filename bricks sync for all files,
not just the one.
Steps to reproduce
- On Linux/Android, create a recipe with a Windows-illegal character in the
name, e.g. Folder/Example "quoted" name.cook, and let it sync up.
- On a Windows client, start sync.
- Observe sync abort with
Check download failed: ... OS error 123; no files
download, including unrelated ones.
Suggested fixes
- Client resilience (most important): a single file's IO error in
check_download_once should not abort the loop. Log a warning, skip that
file, continue with the rest, and surface a non-fatal status to the user.
This downgrades "library bricked" to "one recipe missing."
- Server-side validation at commit: reject or flag paths containing
characters that are invalid on common filesystems. This is the only choke
point that protects every client at once.
- Avoid silently sanitizing/renaming on download — the local indexer would
treat the renamed file as new and re-upload it, creating a duplicate loop.
Skip-and-surface is safer than rename.
Notes
Recovery today requires a server-side DB edit: insert a new record renaming the
path to a valid name (reusing the existing chunk_ids) plus a tombstone for the
old path, so all devices converge. The /list endpoint already returns only the
latest record per path, so this propagates cleanly — but it shouldn't require
manual intervention.
Summary
A single file whose name contains characters that are illegal on the target
filesystem aborts the entire download loop, permanently blocking sync of
the whole library on that device until the offending record is changed
server-side.
Impact
Filenames are validated by neither the server (
/metadata/commit) nor theclient on download. A file created on a permissive filesystem (e.g. Linux/
Android, which allow
",:,|, etc.) is happily stored and listed by theserver. When another device on a stricter filesystem (Windows forbids
< > : " / \ | ? *, trailing dots/spaces, reserved names likeCON) tries todownload it,
File::createfails and the error propagates all the way up:check_download_oncereturns the IO error (syncer.rs, thechunker.save(...)call in the post-download write loop)
download_loopmaps it toSyncError::Unknown("Check download failed: ...")and returns, killing the loop
On Windows this surfaces to users as:
(
ERROR_INVALID_NAME). Because the same record is re-fetched on every retry,the loop never makes progress — one bad filename bricks sync for all files,
not just the one.
Steps to reproduce
name, e.g.
Folder/Example "quoted" name.cook, and let it sync up.Check download failed: ... OS error 123; no filesdownload, including unrelated ones.
Suggested fixes
check_download_onceshould not abort the loop. Log a warning, skip thatfile, continue with the rest, and surface a non-fatal status to the user.
This downgrades "library bricked" to "one recipe missing."
characters that are invalid on common filesystems. This is the only choke
point that protects every client at once.
treat the renamed file as new and re-upload it, creating a duplicate loop.
Skip-and-surface is safer than rename.
Notes
Recovery today requires a server-side DB edit: insert a new record renaming the
path to a valid name (reusing the existing
chunk_ids) plus a tombstone for theold path, so all devices converge. The
/listendpoint already returns only thelatest record per path, so this propagates cleanly — but it shouldn't require
manual intervention.