symfony/translation · error · RuntimeException
Unable to create directory
Error message
Unable to create directory "%s".
What it means
Thrown by FileDumper::dump when mkdir() fails to create the target directory (or its parents) for the translation output file. The call is silenced with @, so any failure (permissions, path exists as file, read-only FS) surfaces as this RuntimeException.
Solutions
- Check/fix filesystem permissions on the parent directory so the PHP process can create it
- Verify no file exists at the directory path; remove or rename it
- Ensure the 'path' option points to a writable, correct location
- If using containers, mount the translations directory as writable by the runtime user
Example fix
// before $dumper->dump($catalogue, ['path' => '/var/www/app/translations']); // /var/www/app not writable // after $dumper->dump($catalogue, ['path' => $kernel->getCacheDir().'/translations']);
Defensive patterns
Strategy: try-catch
Validate before calling
$dir = $options['path']; if (!is_dir($dir) && !@mkdir($dir, 0777, true) && !is_dir($dir)) { throw new \RuntimeException("Cannot prepare output dir {$dir}: ".error_get_last()['message']); } Type guard
function isWritableOutputDir(string $path): bool { return is_dir($path) && is_writable($path); } Try / catch
try { $dumper->dump($catalogue, $options); } catch (RuntimeException $e) { if (str_starts_with($e->getMessage(), 'Unable to create directory')) { // check permissions / run as correct user } throw $e; } Prevention
- Pre-create output directories in deployment scripts with correct ownership
- Ensure the PHP process user owns or has write access to the translations dir
- In Docker, declare the output path as a writable VOLUME and avoid read-only rootfs for it
When it happens
Trigger: dump() computes $fullpath = options['path'] + relative path; the parent directory does not exist and `mkdir($dir, 0777, true)` returns false — e.g. parent dir not writable, path segment is an existing file, or open_basedir restrictions.
Common situations: Writing translations to a directory owned by another user in production; Docker containers running as non-root with read-only volumes; a file existing where a directory is expected; misconfigured path parameter.
Understand the failure class
Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.
Related errors
- The file dumper needs a path option.
- The Translator does not support the following options
- No support implemented for dumping XLIFF version
- Dumping translations in the YAML format requires the…
- The " " file does not exist.
AI-assisted analysis of symfony/translation@ae9e8a51bc (2026-09-15).
Data as JSON: /api/errors/e316b5f5136cb435.
Report an issue: GitHub.
Appendix: source
Thrown at Dumper/FileDumper.php:53
*/
public function setRelativePathTemplate(string $relativePathTemplate): void
{
$this->relativePathTemplate = $relativePathTemplate;
}
public function dump(MessageCatalogue $messages, array $options = []): void
{
if (!\array_key_exists('path', $options)) {
throw new InvalidArgumentException('The file dumper needs a path option.');
}
// save a file for each domain
foreach ($messages->getDomains() as $domain) {
$fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
if (!file_exists($fullpath)) {
$directory = \dirname($fullpath);
if (!file_exists($directory) && !@mkdir($directory, 0o777, true)) {
throw new RuntimeException(\sprintf('Unable to create directory "%s".', $directory));
}
}
$intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
$intlMessages = $messages->all($intlDomain);
if ($intlMessages) {
$intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale());
file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options));
$messages->replace([], $intlDomain);
try {
if ($messages->all($domain)) {
file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
}
continue;
} finally {View on GitHub (pinned to ae9e8a51bc)