passbolt/passbolt_api · error · NotFoundException
$e->getMessage() (avatar stream read failure)
Error message
$e->getMessage() (avatar stream read failure)
What it means
The avatar view endpoint fails to read the avatar image stream from cache/filesystem storage. AvatarsViewController catches any Throwable from AvatarsCacheService::readSteamFromId(), logs it, and rethrows it as a NotFoundException so the HTTP response is a 404 instead of a 500 with a leaked internal message. Any storage backend misconfiguration, missing avatar file, or stream read failure surfaces as this error.
Solutions
- Check server logs for the underlying Throwable message logged by Log::error() — it names the real storage failure.
- Verify the filesystem adapter configuration (base path/credentials) and that the cache directory exists and is writable by the web server user.
- Confirm the avatar record and file exist for the requested id; re-upload the avatar or clear/rebuild the avatar cache.
- If 404 is expected (no avatar), ensure clients fall back to a default avatar image instead of treating it as a bug.
Example fix
// before — raw storage exception surfaces as misleading 404
try {
$stream = $service->readSteamFromId($id, $format);
} catch (Throwable $e) {
Log::error($e->getMessage());
throw new NotFoundException($e->getMessage());
}
// after — fall back to a default avatar stream so storage hiccups don't 404
try {
$stream = $service->readSteamFromId($id, $format);
} catch (Throwable $e) {
Log::error('Avatar read failed: ' . $e->getMessage());
$stream = $service->readDefaultAvatar($format); // or return a placeholder image response
} Defensive patterns
Strategy: fallback
Validate before calling
// client-side pre-check
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId)) {
throw new Error('cannot request avatar: invalid user id');
} Type guard
const isUuid = (v) => typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v); Try / catch
try {
const res = await fetch(`/avatars/view/${userId}.jpg`);
if (res.status === 404) return DEFAULT_AVATAR_URL; // NotFoundException expected when avatar missing/unreadable
if (!res.ok) throw new Error(`avatar fetch failed: ${res.status}`);
return URL.createObjectURL(await res.blob());
} catch (e) {
return DEFAULT_AVATAR_URL;
} Prevention
- Always render a default/placeholder avatar on 404 instead of treating it as a hard failure.
- Verify the avatar cache directory exists and is writable after each deployment.
- Monitor server logs for the underlying Log::error() message to catch storage misconfigurations early.
- Validate the format path segment (.jpg/.png) against supported formats before requesting.
When it happens
Trigger: GET /avatars/view/<id>.jpg (or <id>.png) when the FilesystemAdapter cannot locate or read the avatar file for the given id/format; the underlying adapter throws (e.g. league/flysystem unableToReadFile) and readSteamFromId propagates it.
Common situations: Avatar cache directory not writable or missing on the server; avatar record/file deleted or never generated; wrong storage adapter config (path, permissions, S3 credentials) after environment migration; user id passed has no avatar; deployment wiped webroot/cache between releases.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Registration is not opened to public. Please contact your…
- The avatar id is not valid.
- The comment does not exist.
- The favorite does not exist.
- The group does not exist.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/6a6ac742e198eae7.
Report an issue: GitHub.
Appendix: source
Thrown at src/Controller/Avatars/AvatarsViewController.php:57
* @return \Cake\Http\Response
*/
public function view(
string $id,
string $format,
FilesystemAdapter $filesystemAdapter
): Response {
$formatIsValid = $this->validateImageFormat($format);
if ($formatIsValid === false) {
$id = null;
}
$service = new AvatarsCacheService($filesystemAdapter);
try {
$stream = $service->readSteamFromId($id, $format);
} catch (Throwable $e) {
Log::error($e->getMessage());
throw new NotFoundException($e->getMessage());
}
return $this->getResponse()
->withType('jpg')
->withBody($stream);
}
/**
* Checks if the format provided is medium or small,
* and that the extension is .jpg
*
* @param string $format Image format provided in the requested url.
* @return bool
*/
protected function validateImageFormat(string $format): bool
{
$validFormats = AvatarHelper::getValidImageFormats();
View on GitHub (pinned to 31c1bbc10f)