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

  1. Create the directory: `mkdir -p /app/storage/annotations`
  2. Grant write access to the PHP user: `chown -R www-data:www-data /app/storage/annotations` or `chmod 775` plus group membership
  3. Check open_basedir includes the directory and the volume is not full (`df -h`)
  4. 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

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


AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21). Data as JSON: /api/errors/e8ecf9e4e577acfa. Report an issue: GitHub.