{"record":{"id":"dd06aadc858f12a2","repo":"nextcloud/server","slug":"media-file-not-found","errorCode":null,"errorMessage":"Media file not found","messagePattern":"Media file not found","errorType":"exception","errorClass":"OCSNotFoundException","httpStatus":404,"severity":"error","filePath":"apps/appstore/lib/Controller/DiscoverController.php","lineNumber":138,"sourceCode":"\t\t\t\t24,\n\t\t\t\t60 * 60,\n\t\t\t\t$user,\n\t\t\t);\n\n\t\t\tif (!$this->checkCanDownloadMedia($fileName)) {\n\t\t\t\t$this->logger->warning('Tried to load media files for app discover section from untrusted source');\n\t\t\t\tthrow new OCSBadRequestException('Untrusted media source');\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t$client = $this->clientService->newClient();\n\t\t\t\t$fileResponse = $client->get($fileName);\n\t\t\t\t$contentType = $fileResponse->getHeader('Content-Type');\n\t\t\t\t$extension = $info['extension'] ?? '';\n\t\t\t\t$file = $folder->newFile($hashName . '.' . base64_encode($contentType) . '.' . $extension, $fileResponse->getBody());\n\t\t\t} catch (\\Throwable $e) {\n\t\t\t\t$this->logger->warning('Could not load media file for app discover section', ['media_src' => $fileName, 'exception' => $e]);\n\t\t\t\tthrow new OCSNotFoundException('Media file not found');\n\t\t\t}\n\t\t} else {\n\t\t\t// File was found so we can get the content type from the file name\n\t\t\t$contentType = base64_decode(explode('.', $file->getName())[1] ?? '');\n\t\t}\n\n\t\t$response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $contentType]);\n\t\t// cache for 7 days\n\t\t$response->cacheFor(604800, false, true);\n\t\treturn $response;\n\t}\n\n\tprivate function checkCanDownloadMedia(string $filename): bool {\n\t\t$urlInfo = parse_url($filename);\n\t\tif (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {\n\t\t\treturn false;\n\t\t}\n","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/appstore/lib/Controller/DiscoverController.php#L120-L156","documentation":"OCSNotFoundException (HTTP 404) thrown by DiscoverController::mediaImage when fetching or storing the remote media file fails: the HTTP client get($fileName) throws (unreachable host, 404, timeout) or storing into the appdata folder via newFile fails (disk full, no appdata). The catch wraps any Throwable into this single 'Media file not found' response and logs a warning with the media_src.","triggerScenarios":"Discover section requesting a screenshot whose remote URL is dead (removed from CDN), the app store host unreachable from the server (firewall/egress rules), DNS failure, or the appdata folder for discover media missing/not writable.","commonSituations":"App store CDN hiccups; servers with restricted outbound traffic; cached app metadata referencing screenshots that the publisher deleted; appdata on a full/read-only volume.","solutions":["Retry later if the app store endpoint was transiently unavailable (the response is cached, so a later successful fetch repairs it)","From the server host, verify the failing media_src URL is reachable (curl) — if egress is blocked, allow it","Check the warning log entry 'Could not load media file for app discover section' for the exact media_src and exception","If appdata is broken, ensure the folder exists and is writable; a successful refetch will repopulate it"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function fetchDiscoverMedia(url: string, attempts = 3) {\n\tfor (let i = 0; i < attempts; i++) {\n\t\ttry {\n\t\t\treturn await axios.get(url)\n\t\t} catch (e) {\n\t\t\tif (i === attempts - 1) {\n\t\t\t\t// 404 'Media file not found' — log and fall back to a placeholder image\n\t\t\t\treturn PLACEHOLDER\n\t\t\t}\n\t\t\tawait new Promise((r) => setTimeout(r, 2 ** i * 500)) // backoff before retry\n\t\t}\n\t}\n}","preventionTips":["Treat discover media as best-effort: cache successes and keep a placeholder for failures","Ensure the server can reach the appstore media hosts (egress rules) so first fetches succeed","Watch the 'Could not load media file' warning in the log to spot systematic egress problems"],"tags":["appstore","discover","media","http-client","cache","php"],"backgroundTag":"remote-asset-fetch-failed","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}