phalcon/cphalcon · error · Phalcon\Annotations\Exceptions\AnnotationsDirectoryNotWritable
Annotations directory cannot be written
Error message
Annotations directory cannot be written
What it means
When the Annotations Stream adapter wants to persist a parsed Reflection it writes a serialized blob via file_put_contents into annotationsDir. If that call returns false the adapter throws AnnotationsDirectoryNotWritable. The usual cause is that the cache directory does not exist or the PHP process user lacks write permission on it.
Source
Thrown at phalcon/Annotations/Adapter/Stream.zep:115
return contents;
}
/**
* Writes parsed annotations to files
*/
public function write( string key, <Reflection> data) -> void
{
var code;
string path;
/**
* Paths must be normalized before be used as keys
*/
let path = this->annotationsDir . prepare_virtual_path(key, "_") . ".php",
code = serialize(data);
if unlikely this->phpFilePutContents(path, code) === false {
throw new AnnotationsDirectoryNotWritable();
}
}
}
View on GitHub (pinned to b7419de9cd)
Solutions
- Create the directory: `mkdir -p /app/storage/annotations`
- Grant write access to the PHP user: `chown -R www-data:www-data /app/storage/annotations` or `chmod 775` plus group membership
- Check open_basedir includes the directory and the volume is not full (`df -h`)
- On read-only filesystems (containers) mount a writable volume for the cache or use the Memory adapter
Example fix
// before
$di->set('annotations', function () {
return new \Phalcon\Annotations\Adapter\Stream(['annotationsDir' => '/app/storage/annotations/']);
});
// after: fail fast at boot instead of mid-request
$dir = '/app/storage/annotations/';
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
if (!is_writable($dir)) {
throw new RuntimeException('Annotations directory not writable: ' . $dir);
} Defensive patterns
Strategy: validation
Validate before calling
$dir = rtrim($options['annotationsDir'] ?? '', '/') . '/';
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
if (!is_writable($dir)) {
throw new RuntimeException('Annotations cache dir not writable: ' . $dir);
}
$adapter = new \Phalcon\Annotations\Adapter\Stream(['annotationsDir' => $dir]); Try / catch
try {
$reflector = $annotations->get(Invoices::class);
} catch (\Phalcon\Annotations\Exceptions\AnnotationsDirectoryNotWritable $e) {
// surface as a deployment/infrastructure alert, not a 500 to the user
$logger->error('Annotation cache unwritable: ' . $e->getMessage());
throw $e;
} Prevention
- Create and chmod cache directories in the deployment script, not at runtime
- Run a boot-time is_dir()/is_writable() health check on all configured cache directories
- In containers, mount a writable volume for cache paths
When it happens
Trigger: First `$annotations->get($class)` (cache miss triggers write) where annotationsDir is missing, read-only for the web-server user, blocked by open_basedir, or the disk is full.
Common situations: Deployment forgot to create storage/cache/annotations; directory owned by root while php-fpm runs as www-data; read-only container filesystem; safe_mode/open_basedir restrictions in shared hosting.
Related errors
- Extends compilation file {path} could not be opened
- Cannot read annotation data
- Stream adapter cannot read file: {path}
- Failed to write router cache temp file: {tmpPath}
- Failed to commit router cache: {path}
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/e8ecf9e4e577acfa.
Report an issue: GitHub.