phalcon/cphalcon · critical · Phalcon\Acl\Exceptions\InvalidSnapshot

Malformed ACL snapshot structure

Error message

Malformed ACL snapshot structure

What it means

RedisConnectionFactory::createContext() delegates connect/auth/select to Phalcon\Storage\Adapter\Redis via getAdapter(); any StorageException raised there is rethrown as a generic Queue Exception carrying the storage layer's message and code, with the original chained as previous. So the message you see is the underlying Redis error — e.g. connection refused, AUTH failed, or an invalid database index — repackaged so the queue honors its single throwable contract.

Source

Thrown at phalcon/Acl/Adapter/Storage.zep:106

        let version = data["version"];

        if version != self::SNAPSHOT_VERSION {
            throw new InvalidSnapshot(
                "Incompatible ACL snapshot version '" . version .
                "'; expected '" . self::SNAPSHOT_VERSION . "'"
            );
        }

        if unlikely (
            typeof data["access"] !== "array" ||
            typeof data["accessList"] !== "array" ||
            typeof data["components"] !== "array" ||
            typeof data["componentsNames"] !== "array" ||
            typeof data["roles"] !== "array" ||
            typeof data["roleInherits"] !== "array"
        ) {
            throw new InvalidSnapshot("Malformed ACL snapshot structure");
        }

        let rebuiltRoles = [];
        for name, description in data["roles"] {
            let rebuiltRoles[name] = new Role(name, description);
        }

        let rebuiltComponents = [];
        for name, description in data["components"] {
            let rebuiltComponents[name] = new Component(name, description);
        }

        let this->access                   = data["access"],
            this->accessList               = data["accessList"],
            this->components               = rebuiltComponents,
            this->componentsNames          = data["componentsNames"],
            this->roles                    = rebuiltRoles,
            this->roleInherits             = data["roleInherits"],

View on GitHub (pinned to b7419de9cd)

Solutions

  1. Verify by hand from the app host: redis-cli -h <host> -p <port> -a <auth> -n <index> PING.
  2. Fix the offending option: host, port, auth (password or [user, password]), or index.
  3. Inspect getPrevious() on the caught exception for the exact StorageException cause when the message alone is ambiguous.

Example fix

// before
$factory = new RedisConnectionFactory(['host' => 'redis.internal', 'index' => 15]);
$context = $factory->createContext(); // wraps StorageException (e.g. invalid DB index)

// after
$factory = new RedisConnectionFactory(
    [
        'host' => 'redis.internal',
        'port' => 6379,
        'auth' => [getenv('REDIS_USER'), getenv('REDIS_PASS')],
        'index' => 0, // must be < the server's `databases` setting
    ]
);
$context = $factory->createContext();
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight the exact parameters the factory will use
$redis = new \Redis();
$ok = $redis->connect($options['host'] ?? '127.0.0.1', $options['port'] ?? 6379);
if ($ok && isset($options['auth'])) {
    $ok = $redis->auth($options['auth']); // string or [user, pass] for ACL
}
if ($ok && isset($options['index'])) {
    $ok = $redis->select((int) $options['index']);
}
if (!$ok) {
    throw new \RuntimeException('Redis queue pre-flight failed: ' . $redis->getLastError());
}
$redis->close();

Try / catch

use Phalcon\Queue\Exceptions\Exception as QueueException;

try {
    $context = (new RedisConnectionFactory($options))->createContext();
} catch (QueueException $e) {
    // $e->getMessage() mirrors the storage-layer message (connect/auth/select)
    $cause = $e->getPrevious(); // the original StorageException
    $logger->error('Redis queue connect failed: ' . $e->getMessage(), [
        'exception' => $cause,
    ]);
    throw $e;
}

Prevention

When it happens

Trigger: new RedisConnectionFactory($options)->createContext() with a wrong 'host'/'port', wrong 'auth' password or [user, password] ACL pair, or an 'index' beyond the server's configured databases; Redis down or unreachable.

Common situations: Env-driven config mismatches between environments; Redis enabling requirepass/ACL after launch; SELECT on index 15 when databases=8; failover where the old node is gone; stale persistent connections.

Understand the failure class

Related errors


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