Summary
download_files (both AsyncDaytona and Daytona) fails to download any file whose remote path contains non-ASCII characters when the runtime has python-multipart >= 0.0.30 installed. With python-multipart <= 0.0.29 the same download succeeds. Uploading non-ASCII filenames is unaffected — only download breaks.
The failure surfaces as, e.g.:
DaytonaError: Failed to download file: Failed to download files: '/tmp/___.txt'
where ___ is the ASCII-fallback rendering of the original non-ASCII name.
Environment
daytona Python SDK: reproduced on 0.187.0; the relevant parse code is unchanged through latest 0.190.1.
python-multipart: >= 0.0.30 broken, <= 0.0.29 works. (It's a transitive dependency that downstream users routinely upgrade for security fixes — e.g. the high-severity CVE-2026-53539, fixed in 0.0.30.)
- Python 3.12
What happens
The download response is multipart/form-data, and each part identifies its file via the Content-Disposition header. Captured from a download response for /tmp/あああ.txt:
Content-Disposition: form-data; name="file"; filename="/tmp/___.txt"; filename*=utf-8''%2Ftmp%2F%E3%81%82%E3%81%82%E3%81%82.txt
filename = /tmp/___.txt — an ASCII-only fallback (non-ASCII chars dropped or replaced with _)
filename* = percent-encoded UTF-8 = the real /tmp/あああ.txt
The SDK correlates each response part back to the request by key-matching on the path, and that key is the bare filename — so the requested path (written as the key) and the parsed path (read back as the key) must be identical:
daytona/_async/filesystem.py:412 — src_file_meta_dict[f.source] = ... — builds the request map keyed by the requested path = /tmp/あああ.txt
daytona/_async/filesystem.py:503 — source = cd_params.get(b"filename", ...) — reads the part's bare filename. With filename* ignored, this is the ASCII fallback with non-ASCII chars dropped = /tmp/___.txt, not the requested path
daytona/_async/filesystem.py:512 — meta = src_file_meta_dict[source] — looks the part up by that key → KeyError('/tmp/___.txt'), because /tmp/___.txt was never a key (the map holds /tmp/あああ.txt)
daytona/_sync/filesystem.py — same pattern; filename* is read nowhere in the SDK (through 0.190.1).
python-multipart 0.0.30 deliberately stopped exposing filename* (#291), since RFC 7578 §4.2 forbids it in multipart/form-data — so the bare ASCII filename is all the SDK can read.
Reproduction
A minimal script — runs as-is with daytona and python-multipart >= 0.0.30 installed and DAYTONA_API_KEY set in the environment:
import asyncio
from daytona import AsyncDaytona
FILENAMES = [
'/tmp/happy_path.txt', # ASCII
'/tmp/café.txt', # Latin-1 accented (single non-ASCII char)
'/tmp/あああ.txt', # Japanese
]
CONTENT = b'roundtrip-content'
async def main() -> int:
success_count = 0
async with AsyncDaytona() as daytona:
sandbox = await daytona.create(timeout=120)
try:
for path in FILENAMES:
# Upload always works; download is the failing direction.
await sandbox.fs.upload_file(CONTENT, path)
try:
got = await sandbox.fs.download_file(path)
if got == CONTENT:
success_count += 1
status = 'PASS'
else:
status = f'FAIL (content mismatch: {got!r})'
except Exception as e:
status = f'FAIL ({type(e).__name__}: {e})'
print(f'{path:30} {status}')
finally:
await sandbox.delete()
print(f'{success_count}/{len(FILENAMES)} downloaded successfully')
if __name__ == '__main__':
asyncio.run(main())
Output with python-multipart >= 0.0.30 (only the ASCII name survives):
/tmp/happy_path.txt PASS
/tmp/café.txt FAIL (DaytonaError: Failed to download file: Failed to download files: '/tmp/caf.txt')
/tmp/あああ.txt FAIL (DaytonaError: Failed to download file: Failed to download files: '/tmp/___.txt')
1/3 downloaded successfully
With python-multipart <= 0.0.29 all three pass. Uploading the same names always succeeds — only download breaks.
Affects all non-ASCII filenames (not script-specific)
Swept across scripts; every non-ASCII name fails because the bare filename is an ASCII-only fallback while the real name lives only in the now-ignored filename*:
| requested filename |
bare filename seen by SDK |
result |
café.txt |
caf.txt (é dropped) |
FAIL |
あああ.txt |
___.txt |
FAIL |
안녕.txt |
__.txt |
FAIL |
中文.txt |
__.txt |
FAIL |
Привет.txt |
______.txt |
FAIL |
Ελληνικά.txt |
________.txt |
FAIL |
café.txt (a single Latin-1 character) fails too, so this is any non-ASCII filename, not a CJK/multibyte-specific issue.
Expected behavior
Downloading a file with a non-ASCII path should succeed regardless of the installed python-multipart version. (Uploading such files already works, so the round-trip is currently asymmetric.) We're not sure of the best place to fix this given the SDK ↔ daemon split, so we'll leave the approach to the maintainers — the key observation is just that the SDK currently relies on the bare filename, while the only faithful copy of the name is in filename*, which python-multipart >= 0.0.30 no longer exposes.
References
Summary
download_files(bothAsyncDaytonaandDaytona) fails to download any file whose remote path contains non-ASCII characters when the runtime haspython-multipart >= 0.0.30installed. Withpython-multipart <= 0.0.29the same download succeeds. Uploading non-ASCII filenames is unaffected — only download breaks.The failure surfaces as, e.g.:
where
___is the ASCII-fallback rendering of the original non-ASCII name.Environment
daytonaPython SDK: reproduced on 0.187.0; the relevant parse code is unchanged through latest 0.190.1.python-multipart: >= 0.0.30 broken, <= 0.0.29 works. (It's a transitive dependency that downstream users routinely upgrade for security fixes — e.g. the high-severity CVE-2026-53539, fixed in 0.0.30.)What happens
The download response is
multipart/form-data, and each part identifies its file via theContent-Dispositionheader. Captured from a download response for/tmp/あああ.txt:filename=/tmp/___.txt— an ASCII-only fallback (non-ASCII chars dropped or replaced with_)filename*= percent-encoded UTF-8 = the real/tmp/あああ.txtThe SDK correlates each response part back to the request by key-matching on the path, and that key is the bare
filename— so the requested path (written as the key) and the parsed path (read back as the key) must be identical:daytona/_async/filesystem.py:412—src_file_meta_dict[f.source] = ...— builds the request map keyed by the requested path =/tmp/あああ.txtdaytona/_async/filesystem.py:503—source = cd_params.get(b"filename", ...)— reads the part's barefilename. Withfilename*ignored, this is the ASCII fallback with non-ASCII chars dropped =/tmp/___.txt, not the requested pathdaytona/_async/filesystem.py:512—meta = src_file_meta_dict[source]— looks the part up by that key →KeyError('/tmp/___.txt'), because/tmp/___.txtwas never a key (the map holds/tmp/あああ.txt)daytona/_sync/filesystem.py— same pattern;filename*is read nowhere in the SDK (through 0.190.1).python-multipart 0.0.30deliberately stopped exposingfilename*(#291), since RFC 7578 §4.2 forbids it inmultipart/form-data— so the bare ASCIIfilenameis all the SDK can read.Reproduction
A minimal script — runs as-is with
daytonaandpython-multipart >= 0.0.30installed andDAYTONA_API_KEYset in the environment:Output with
python-multipart >= 0.0.30(only the ASCII name survives):With
python-multipart <= 0.0.29all three pass. Uploading the same names always succeeds — only download breaks.Affects all non-ASCII filenames (not script-specific)
Swept across scripts; every non-ASCII name fails because the bare
filenameis an ASCII-only fallback while the real name lives only in the now-ignoredfilename*:filenameseen by SDKcafé.txtcaf.txt(é dropped)あああ.txt___.txt안녕.txt__.txt中文.txt__.txtПривет.txt______.txtΕλληνικά.txt________.txtcafé.txt(a single Latin-1 character) fails too, so this is any non-ASCII filename, not a CJK/multibyte-specific issue.Expected behavior
Downloading a file with a non-ASCII path should succeed regardless of the installed
python-multipartversion. (Uploading such files already works, so the round-trip is currently asymmetric.) We're not sure of the best place to fix this given the SDK ↔ daemon split, so we'll leave the approach to the maintainers — the key observation is just that the SDK currently relies on the barefilename, while the only faithful copy of the name is infilename*, whichpython-multipart >= 0.0.30no longer exposes.References