{"record":{"id":"5eadca7af71a4385","repo":"PHPOffice/PhpSpreadsheet","slug":"could-not-close-file-after-writing","errorCode":null,"errorMessage":"Could not close file after writing.","messagePattern":"Could not close file after writing\\.","errorType":"exception","errorClass":"PhpOffice\\PhpSpreadsheet\\Writer\\Exception","httpStatus":null,"severity":"error","filePath":"src/PhpSpreadsheet/Writer/BaseWriter.php","lineNumber":138,"sourceCode":"        }\n\n        $this->fileHandle = $fileHandle;\n        $this->shouldCloseFile = true;\n    }\n\n    protected function tryClose(): bool\n    {\n        return fclose($this->fileHandle);\n    }\n\n    /**\n     * Close file handle only if we opened it ourselves.\n     */\n    protected function maybeCloseFileHandle(): void\n    {\n        if ($this->shouldCloseFile) {\n            if (!$this->tryClose()) {\n                throw new Exception('Could not close file after writing.');\n            }\n        }\n    }\n}\n","sourceCodeStart":120,"sourceCodeEnd":143,"githubUrl":"https://github.com/PHPOffice/PhpSpreadsheet/blob/65b080eef4d9fd11a5796135ab145883e5c3d6a6/src/PhpSpreadsheet/Writer/BaseWriter.php#L120-L143","documentation":"After writing, maybeCloseFileHandle() calls fclose() on handles the writer opened itself (shouldCloseFile). fclose returns false when pending buffers cannot be flushed, typically disk full, quota exceeded, or network storage dropping mid-write, and the writer converts that into 'Could not close file after writing.' Any file that hits this should be treated as incomplete or corrupt.","triggerScenarios":"save() onto a filesystem that fills up during the write; hosting quota exceeded partway through a large export; NFS/SMB share disconnecting before the final flush.","commonSituations":"Containers with small ephemeral disks; shared hosting with file quotas; batch jobs that accumulate exports until the volume is full; very large spreadsheets whose final flush exceeds remaining space.","solutions":["Free space or raise the quota, then regenerate the export; do not ship the partial file.","Pre-check with disk_free_space(dirname($path)) against a rough size estimate before saving.","Write to local temp storage first, then copy the finished file to network destinations.","Prune old exports in batch jobs so the volume stays below capacity."],"exampleFix":"// before\n$writer->save('/mnt/nfs/exports/report.xlsx'); // storage fills or drops mid-write\n\n// after\n$tmp = tempnam(sys_get_temp_dir(), 'xlsx');\n$writer->save($tmp); // local disk first\ncopy($tmp, '/mnt/nfs/exports/report.xlsx');\nunlink($tmp);","handlingStrategy":"try-catch","validationCode":"$needed = 100 * 1024 * 1024; // rough upper bound for the export\n$free = disk_free_space(dirname($path));\nif ($free === false || $free < $needed) {\n    throw new RuntimeException('Insufficient disk space for export');\n}\n$writer->save($path);","typeGuard":null,"tryCatchPattern":"use PhpOffice\\PhpSpreadsheet\\Writer\\Exception as WriterException;\n\ntry {\n    $writer->save($path);\n} catch (WriterException $e) {\n    if (str_contains($e->getMessage(), 'Could not close file')) {\n        unlink($path); // discard the partial, corrupt output\n        // alert the operator: disk full or storage dropped mid-write\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Monitor free space wherever exports land and alert before it reaches zero.","Write large exports to local temp storage first, then copy to network destinations.","After any close failure, treat the file as corrupt: delete and regenerate rather than deliver it."],"tags":["phpspreadsheet","writer","file-io","disk-full","save"],"backgroundTag":"file-write-failure","analyzedSha":"65b080eef4d9fd11a5796135ab145883e5c3d6a6","analyzedAt":"2026-08-17T05:40:41.646Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}