ankitects/anki · error · ValueError
Unexpected content-disposition header: {resp.headers.get('co
Error message
Unexpected content-disposition header: {resp.headers.get('content-disposition')} What it means
download_addon() in qt/aqt/addons.py fetches an add-on package from AnkiWeb and derives the package filename from the HTTP `content-disposition` response header, expecting the exact form `attachment; filename=<name>`. When the header is missing entirely (KeyError on direct dict access) or does not match the regex `attachment; filename=(.+)`, a ValueError with the unexpected header value is raised. The exception is caught by download_addon's own `except Exception` and wrapped in a DownloadError, so callers see a failed download rather than a crash.
Source
Thrown at qt/aqt/addons.py:1122
# Downloading
######################################################################
def download_addon(client: HttpClient, id: int) -> DownloadOk | DownloadError:
"Fetch a single add-on from AnkiWeb."
try:
resp = client.get(f"{aqt.appShared}download/{id}?v=2.1&p={_current_version}")
if resp.status_code != 200:
return DownloadError(status_code=resp.status_code)
data = client.stream_content(resp)
match = re.match(
"attachment; filename=(.+)", resp.headers["content-disposition"]
)
if match is None:
raise ValueError(
f"Unexpected content-disposition header: {resp.headers.get('content-disposition')}"
)
fname = match.group(1)
meta = extract_meta_from_download_url(resp.url)
return DownloadOk(
data=data,
filename=fname,
mod_time=meta.mod_time,
min_point_version=meta.min_point_version,
max_point_version=meta.max_point_version,
branch_index=meta.branch_index,
)
except Exception as e:
return DownloadError(exception=e)
View on GitHub (pinned to 2fae55543c)
Solutions
- Retry the download later / check network setup (proxy, VPN, hosts file) to confirm you are actually reaching AnkiWeb and receiving the real add-on response.
- Upgrade Anki to the latest version so any server-side header-format changes are handled by updated parsing code in addons.py.
- Install the add-on manually: download the .ankiaddon file in a browser and use Tools > Add-ons > Install from file.
- Patch/wrap download_addon to parse the header more tolerantly (accept filename*=/quoted forms) or fall back to a constructed filename like `<id>.ankiaddon`.
Example fix
// before
match = re.match(
"attachment; filename=(.+)", resp.headers["content-disposition"]
)
if match is None:
raise ValueError(
f"Unexpected content-disposition header: {resp.headers.get('content-disposition')}"
)
fname = match.group(1)
// after
disposition = resp.headers.get("content-disposition", "")
match = re.match("attachment; filename\*?=(?:UTF-8''|\"?)([^\";]+)", disposition)
if match is None:
# tolerate missing header by deriving filename from the add-on id
fname = f"{id}.ankiaddon"
else:
fname = match.group(1) Defensive patterns
Strategy: validation
Validate before calling
disposition = resp.headers.get("content-disposition", "")
if not re.match("attachment; filename=.+", disposition):
print(f"Skipping download: bad content-disposition {disposition!r}")
return DownloadError(exception=ValueError(disposition)) Type guard
def is_attachment_disposition(headers) -> bool:
return bool(re.match("attachment; filename=.+", headers.get("content-disposition", ""))) Try / catch
result = download_addon(client, addon_id)
if isinstance(result, DownloadError):
showWarning(f"Add-on download failed: {result.exception or result.status_code}")
# note: download_addon already wraps the ValueError in DownloadError Prevention
- Always check the DownloadError/DownloadOk union result instead of assuming success — download_addon swallows all exceptions into DownloadError.
- Verify you reach the real AnkiWeb host (no proxy/captive-portal HTML) before downloading add-ons.
- When reimplementing the download, handle the missing header and RFC 5987 filename*= variants defensively.
- Keep Anki updated so server-side header changes are matched by parsing fixes.
When it happens
Trigger: Calling download_addon (or download_and_install_addon) when the AnkiWeb response's content-disposition header is absent, empty, or not of the form `attachment; filename=...` — e.g. a proxy/CDN stripping the header, a redirect/interstitial HTML page served instead of the .ankiaddon binary, or a server change to header formatting (RFC 5987 `filename*=UTF-8''...` form, quoted filename with semicolons, or `inline` disposition).
Common situations: Corporate proxies, antivirus gateways, or captive portals intercepting the AnkiWeb download and returning their own response without the header; AnkiWeb server-side changes or CDN misconfiguration; mirror/hosts-file overrides pointing appShared at a different server; heavily encoded or quoted filenames the naive regex can't parse.
Related errors
AI-assisted analysis of ankitects/anki@2fae55543c (2026-09-12).
Data as JSON: /api/errors/fbfc4f3c7c31051b.
Report an issue: GitHub.