{"record":{"id":"fbacecb32207da75","repo":"phalcon/cphalcon","slug":"last-php-error-message","errorCode":null,"errorMessage":"{last PHP error message}","messagePattern":"\\{last PHP error message\\}","errorType":"exception","errorClass":"Phalcon\\Session\\Adapter\\Exceptions\\AdapterRuntimeError","httpStatus":null,"severity":"error","filePath":"phalcon/Session/Adapter/Stream.zep","lineNumber":140,"sourceCode":"     * @return false|int\n     * @throws AdapterRuntimeError\n     */\n    public function gc(int max_lifetime) -> false | int\n    {\n        var file, glob, last, pattern, time;\n\n        let pattern = this->path . this->prefix . \"*\",\n            time    = time() - max_lifetime,\n            glob    = this->getGlobFiles(pattern);\n\n        if (false === glob) {\n            let last = error_get_last();\n            if (isset(last[\"message\"])) {\n                let last = last[\"message\"];\n            } else {\n                let last = \"Unexpected gc error\";\n            }\n            throw new AdapterRuntimeError(last);\n        }\n\n        if (!empty(glob)) {\n            for file in glob {\n                if true === this->phpFileExists(file) &&\n                   true === is_file(file)     &&\n                   (filemtime(file) < time) {\n                    this->phpUnlink(file);\n                }\n            }\n        }\n\n        return 1;\n    }\n\n    /**\n    * Ignore the savePath and use local defined path\n    */","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/phalcon/cphalcon/blob/b7419de9cd0a8a3f48441ead84c9f8415d463e25/phalcon/Session/Adapter/Stream.zep#L122-L158","documentation":"Stream::gc() globs '{savePath}/{prefix}*' to collect expired session files. glob() errors are suppressed (error_reporting(0) + error_clear_last), and when glob() still returns false the adapter throws AdapterRuntimeError carrying the last PHP error message (or 'Unexpected gc error' if none was recorded). The message is the raw PHP warning, e.g. an open_basedir restriction or permission denied on directory read.","triggerScenarios":"Garbage collection runs on session_start() when session.gc_probability/session.gc_divisor hit, and the save directory has become unreadable since construction: deleted by a tmp cleaner while the worker still runs, permissions changed, open_basedir tightened, the path replaced by a file, or an unmounted network share.","commonSituations":"systemd-tmpfiles or docker tmp cleaners removing the session directory under long-lived workers; ops tightening open_basedir after the app bootstrapped; NFS/overlay filesystems returning glob errors; directories removed between deploys.","solutions":["Read the exception message: it names the real PHP-level cause (open_basedir restriction, permission denied, No such file or directory)","Recreate the directory and restore list+write permissions for the PHP user: mkdir -p and chmod/chown as for error 701","Move savePath to a dedicated directory that tmp cleaners ignore, and register it in tmpfiles.d with an age rule matching session lifetime","Add a health check that is_dir($savePath) && is_readable($savePath) && is_writable($savePath) before starting the session"],"exampleFix":"// before: gc explodes mid-request when the dir vanished\n// after: self-heal before session start\n$dir = '/var/lib/myapp/sessions';\nif (!is_dir($dir)) { @mkdir($dir, 0770, true); }\n$session->setAdapter(new Stream(['savePath' => $dir]));","handlingStrategy":"try-catch","validationCode":"if (!is_dir($savePath) || !is_readable($savePath) || !is_writable($savePath)) {\n    // recreate or alert before the session (and its gc) runs\n    @mkdir($savePath, 0770, true);\n}","typeGuard":null,"tryCatchPattern":"try {\n    $session->start();\n} catch (\\Phalcon\\Session\\Adapter\\Exceptions\\AdapterRuntimeError $e) {\n    // $e->getMessage() is the raw glob() warning (open_basedir, permission denied, ...)\n    $logger->error('Session gc failed: ' . $e->getMessage());\n    // self-heal the directory and reject the request safely\n    throw new ServiceUnavailableException('Session storage unavailable', 0, $e);\n}","preventionTips":["Exclude the session directory from tmp cleaners (tmpfiles.d rules) or align the cleaning age with session lifetime","Monitor for disappearance of the savePath directory in long-lived workers","Treat AdapterRuntimeError messages as the source of truth: they are the suppressed PHP warnings from glob()"],"tags":["php","phalcon","session","glob","gc","filesystem"],"backgroundTag":"permission-denied","analyzedSha":"b7419de9cd0a8a3f48441ead84c9f8415d463e25","analyzedAt":"2026-08-21T06:21:18.811Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}