(Also if it wasnt obvious I used AI for a bunch of these because im not that code literate yet, please excuse any mistakes)
Environment:
- OS: Debian 13 (headless)
- Python: 3.13
- Anki Version: 26.8.1, 26.9.3 (
ankivia PyPI) - Comparison: Anki Desktop 26.8.1 on macOS syncs fine using the same account.
Description:
Calling col.full_upload_or_download(..., upload=False) on a headless Python instance fails instantly (~170ms) with a Rust HTTP 400 error (missing original size).
This occurs on an empty temporary collection, ruling out local data/media corruption or proxy issues.
The reproduction script:
#!/usr/bin/env python3
#Requires: pip install anki
import os
import tempfile
from anki.collection import Collection
from anki.sync_pb2 import SyncCollectionResponse
EMAIL = "YOUR-ANKIWEB-EMAIL"
PASSWORD = "YOUR-ANKIWEB-PASSWORD"
tmp = tempfile.mkdtemp(prefix="freshanki")
path = os.path.join(tmp, "collection.anki2")
col = Collection(path)
try:
auth = col.sync_login(username=EMAIL, password=PASSWORD, endpoint=None)
out = col.sync_collection(auth, sync_media=False)
print("required:", out.required)
print("host:", getattr(out, "host_number", None))
print("endpoint:", getattr(out, "new_endpoint", None))
print("media_usn:", getattr(out, "server_media_usn", None))
assert out.required in (
SyncCollectionResponse.FULL_SYNC,
SyncCollectionResponse.FULL_DOWNLOAD,
), "account does not currently require a full download; repro needs that state"
col.close_for_full_sync()
try:
col.full_upload_or_download(
auth=auth, server_usn=out.server_media_usn, upload=False
)
print("FULL-DOWNLOAD-OK")
finally:
col.reopen(after_full_sync=True)
finally:
col.close()
Log file:
2026-09-26 09:37:17,863 full-download start server_media_usn=28329 2026-09-26 09:37:18,035 full-download FAILED 'HttpError { code: 400, context: "missing original size", source: None }' 2026-09-26 09:37:28,750 handshake required=2 host_number=21 new_endpoint='``https://sync21.ankiweb.net/``' server_media_usn=28329 2026-09-26 09:37:28,750 full-download start server_media_usn=28329 2026-09-26 09:37:28,923 full-download FAILED 'HttpError { code: 400, context: "missing original size", source: None }' 2026-09-26 09:37:44,638 handshake required=2 host_number=21 new_endpoint='``https://sync21.ankiweb.net/``' server_media_usn=28329 2026-09-26 09:37:44,638 full-download start server_media_usn=28329 2026-09-26 09:37:44,811 full-download FAILED 'HttpError { code: 400, context: "missing original size", source: None }' 2026-09-26 09:38:05,522 handshake required=2 host_number=21 new_endpoint='``https://sync21.ankiweb.net/``' server_media_usn=28329 2026-09-26 09:38:05,522 full-download start server_media_usn=28329 2026-09-26 09:38:05,696 full-download FAILED 'HttpError { code: 400, context: "missing original size", source: None }' 2026-09-26 09:50:35,987 handshake required=2 host_number=21 new_endpoint='``https://sync21.ankiweb.net/``' server_media_usn=28329 2026-09-26 09:50:35,988 full-download start server_media_usn=28329 2026-09-26 09:50:36,161 full-download FAILED 'HttpError { code: 400, context: "missing original size", source: None }' 2026-09-26 09:50:41,872 handshake required=0 host_number=21 new_endpoint='``https://sync21.ankiweb.net/``' server_media_usn=28329
Additional Context:
If files (collection.anki2 and media) are manually copied from macOS to the VM, normal incremental syncs (and adding cards directly on the VM) work fine.
Edit: I was not able to fix the issue but found a workaround. When the Rust backend call 400s, I re-issue the same full download over plain HTTPS (zstd body + anki-sync header, follow redirects), verify the bytes open as sqlite, and swap the collection file. Empty-collection repro proves it’s request framing, not data.