libnyanpasu/clash-nyanpasu · warning
no content-type
Error message
no content-type
What it means
cache_icon_inner downloads an icon over HTTP and needs the response's content-type header to determine the MIME type for the cache file. If the server returns a 2xx response without a Content-Type header, it throws anyhow "no content-type".
Solutions
- Fix the icon server to send a correct Content-Type header.
- Point the icon URL at a host serving proper MIME types.
- Fall back to a sniffed or default MIME type when the header is missing.
- Add the header via a reverse proxy in front of the icon host.
Example fix
// before
let mime = response.headers().get("content-type").ok_or(anyhow!("no content-type"))?.to_str()?.to_string();
// after
let mime = response.headers().get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream")
.to_string(); Defensive patterns
Strategy: fallback
Try / catch
try {
await invoke('cache_icon', { url });
} catch (e) {
if (String(e).includes('no content-type')) {
usePlaceholderIcon(url);
} else throw e;
} Prevention
- Host icons on servers that send proper MIME headers
- Prefer well-known CDN/image hosts for icon URLs
- Configure nginx mime.types correctly on self-hosted icons
When it happens
Trigger: Caching a proxy-group/provider icon whose URL points to a server (bare object store, misconfigured CDN/nginx, raw file server) that omits Content-Type on a successful response.
Common situations: Subscription providers hosting icons on servers with no MIME types configured, self-hosted file servers, or services replying with headerless 200s.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/f25b04a7e20f2b95.
Report an issue: GitHub.
Appendix: source
Thrown at backend/tauri/src/server/mod.rs:113
Some(_) => {
let span = tracing::span!(tracing::Level::DEBUG, "read_cache_file", path = ?cache_file);
let _enter = span.enter();
match read_cache_file(&cache_file).await {
Ok((mime, bytes)) => return Ok((mime, bytes)),
Err(e) => {
tracing::error!("failed to read cache file: {}", e);
remove_cache_file(&cache_file).await;
}
}
}
_ => (),
}
let client = get_reqwest_client()?;
let response = client.get(url).send().await?.error_for_status()?;
let mime = response
.headers()
.get("content-type")
.ok_or(anyhow!("no content-type"))?
.to_str()?
.to_string();
let bytes = response.bytes().await?;
let data = CacheFile {
mime: Cow::Owned(mime),
bytes,
};
if let Err(e) = write_cache_file(&cache_file, &data).await {
tracing::error!("failed to write cache file: {}", e);
}
Ok(data
.try_into()
.expect("It's impossible to fail, if failed, it must a bug, or memory corruption"))
}
#[tracing_attributes::instrument]
async fn cache_icon(query: Query<CacheIcon>) -> Response<Body> {View on GitHub (pinned to f7dbce2997)