@@ -175,6 +175,19 @@ private function loadComposerAutoloader(Modules $modules): void
175175 * 2. PSR-4 autoloader
176176 * 3. Non-class files
177177 *
178+ * Both loaders are prepended so they run before Composer's autoloader.
179+ * Composer registers itself with prepend=true, giving it position 0.
180+ * We prepend PSR-4 first, then classmap, so the final SPL order is:
181+ * [CI classmap] → [CI PSR-4] → [Composer]
182+ *
183+ * This matters because Composer maps the CodeIgniter\ namespace using a
184+ * path with ".." traversal (__DIR__ . "/../../system"). PHP's include()
185+ * resolves that traversal via realpath(), which can fail transiently on
186+ * Docker overlayfs under concurrent startup load. CI's own paths use the
187+ * pre-resolved SYSTEMPATH constant (no ".." components), so they are more
188+ * reliable. Giving CI priority ensures CodeIgniter\ classes are loaded
189+ * via the clean path before Composer's ".." path is attempted.
190+ *
178191 * @return void
179192 */
180193 public function register ()
@@ -188,8 +201,11 @@ public function register()
188201 $ this ->registeredClosures [] = $ loadClassmap ;
189202 $ this ->registeredClosures [] = $ loadClass ;
190203
191- spl_autoload_register ($ loadClassmap , true );
192- spl_autoload_register ($ loadClass , true );
204+ // Prepend PSR-4 first, then classmap — because each prepend pushes
205+ // everything else back, so the last-prepended handler ends up first.
206+ // Result: [CI classmap] → [CI PSR-4] → [Composer] → ...
207+ spl_autoload_register ($ loadClass , true , true );
208+ spl_autoload_register ($ loadClassmap , true , true );
193209
194210 foreach ($ this ->files as $ file ) {
195211 $ this ->includeFile ($ file );
@@ -342,18 +358,6 @@ protected function includeFile(string $file)
342358 return $ file ;
343359 }
344360
345- // Retry once: is_file() can return false transiently under high I/O
346- // load (e.g., concurrent process startup on CI runners). Flushing the
347- // stat cache ensures the second attempt makes a fresh kernel stat()
348- // call rather than reusing a stale negative result.
349- clearstatcache (true , $ file );
350-
351- if (is_file ($ file )) {
352- include_once $ file ;
353-
354- return $ file ;
355- }
356-
357361 return false ;
358362 }
359363
0 commit comments