Leantime/leantime · error · Symfony\Component\Filesystem\Exception\FileNotFoundException
File not included in request or has invalid format
Error message
File not included in request or has invalid format
What it means
upload() requires the $file argument to contain the uploaded file under the exact key 'file' (i.e. a $_FILES-shaped array whose file input is named 'file'). If $file['file'] is missing or not an array it throws Symfony's FileNotFoundException (Symfony\Component\Filesystem\Exception\FileNotFoundException) — crucially this is NOT Leantime's FileValidationException, so the adjacent catch block does not catch it and the exception propagates out of upload() as an unhandled 500 / JSON-RPC error envelope.
Source
Thrown at app/Domain/Files/Services/Files.php:112
/**
* @throws BindingResolutionException
*
* @api
*/
public function upload($file, $module, $moduleId, $entity = null, $disk = 'default'): array|string|false
{
try {
// Validate input parameters
if (empty($module) || empty($moduleId)) {
Log::warning('Upload attempted with missing module or moduleId', [
'module' => $module,
'moduleId' => $moduleId,
]);
throw new FileValidationException('Missing module or moduleId', FileValidationException::VALIDATION_ERROR);
}
if (! isset($file['file']) || ! is_array($file['file'])) {
throw new FileNotFoundException('File not included in request or has invalid format');
}
} catch (FileValidationException $e) {
Log::warning('File validation failed: '.$e->getMessage());
return $e->getUserMessage();
}
// Normalize module names for consistency
if ($module === 'projects') {
$module = 'project';
}
if ($module === 'tickets') {
$module = 'ticket';
}
// Authorize against the target's owning project before writing anything (commenter+;
// admin/owner bypass). This guards the JSON-RPC path, which reaches the @api upload()
// directly, without the Upload controller's userCanUploadToModule pre-check.View on GitHub (pinned to 9a9f49f100)
Solutions
- Name the file input exactly 'file' so PHP populates $_FILES['file'] and pass $_FILES as $file
- Verify isset($file['file']) && is_array($file['file']) before calling upload
- If you cannot control the input name, remap it first ($_FILES['file'] = $_FILES['attachment']) and wrap the call in a try/catch for Symfony FileNotFoundException
Example fix
<!-- before --> <input type="file" name="attachment"> <!-- after --> <input type="file" name="file">
Defensive patterns
Strategy: try-catch
Validate before calling
if (! isset($file['file']) || ! is_array($file['file'])) {
// upload() would throw an uncaught Symfony FileNotFoundException here
throw new \InvalidArgumentException("File must be sent as a multipart field named 'file'");
} Type guard
function hasUploadedFileAtFileKey(array $file): bool
{
return isset($file['file']) && is_array($file['file']);
} Try / catch
try {
$result = $filesService->upload($_FILES, $module, $moduleId);
} catch (\Symfony\Component\Filesystem\Exception\FileNotFoundException $e) {
// NOT caught inside upload() — it propagates; fix the form's file input name to 'file'
log::error($e); // per repo convention use Log facade
return response('Upload must include a file field named "file"', 400);
} Prevention
- Name the file input exactly 'file' in every upload form — the service reads $file['file']
- Know the trap: this Symfony exception is NOT converted to a friendly string like FileValidationException is
- If PHP silently drops uploads (post_max_size exceeded), $_FILES is empty and hits this same path — check Content-Length first
When it happens
Trigger: An upload form whose file input is named anything other than 'file' (e.g. 'attachment') produces $file['attachment']; passing a bare Symfony UploadedFile object or a scalar instead of a $_FILES array; a multipart POST that never reached PHP intact so $_FILES is empty.
Common situations: Renaming the file input during a frontend redesign; API clients posting the file at the top level of the request instead of as the multipart field 'file'; requests silently stripped by post_max_size exhaustion (PHP drops $_FILES); test harnesses with hand-built fake arrays missing the 'file' key.
Related errors
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/81b729831b0051bb.
Report an issue: GitHub.