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
- Verify by hand from the app host: redis-cli -h <host> -p <port> -a <auth> -n <index> PING.
- Fix the offending option: host, port, auth (password or [user, password]), or index.
- 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
- Validate host/port/auth/index once at boot with a PING-style pre-flight.
- Keep 'index' below the server's `databases` setting across all environments.
- Log getPrevious() — the wrapped StorageException carries the precise Redis error.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Access '{access}' does not exist in component '{componentNam
- Stream adapter file does not exist: {path}
- Stream adapter file is not valid JSON: {path}
- Stream adapter file does not contain a JSON array: {path}
- Session guard 'name' and 'rememberName' must differ
AI-assisted analysis of phalcon/cphalcon@b7419de9cd (2026-08-21).
Data as JSON: /api/errors/f9ad351c55a8a66e.
Report an issue: GitHub.