﻿id	summary	reporter	owner	description	type	status	priority	milestone	component	version	severity	resolution	keywords	cc	focuses
65911	wp_is_maintenance_mode() can require a deleted .maintenance file with opcache.enable_file_override=1	cbratschi		"= Summary =

After an update has otherwise completed successfully, WordPress can fail during shutdown with:

{{{
Uncaught Error: Failed opening required '<wordpress-root>/.maintenance'
in <wordpress-root>/wp-includes/load.php

wp_is_maintenance_mode()
WP_Fatal_Error_Handler::handle()
}}}

The error disappears on reload, the update is installed, and the .maintenance file is no longer present.

This has occurred intermittently on several installations since 2025. It is reproducible when PHP-FPM uses OPcache with opcache.enable_file_override=1.

= Why no earlier fatal is required =

WP_Fatal_Error_Handler::handle() calls wp_is_maintenance_mode() before detect_error():

https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/class-wp-fatal-error-handler.php

The handler runs at every shutdown. Reaching this stack therefore does not prove that an earlier fatal error occurred.

wp_is_maintenance_mode() performs a separate file_exists() check followed by require:

{{{
if ( ! file_exists( ABSPATH . '.maintenance' ) || wp_installing() ) {
    return false;
}

require ABSPATH . '.maintenance';
}}}

With opcache.enable_file_override=1, file_exists() can return true from a cached script entry after another FPM request has deleted the physical file. require then reaches the filesystem and fails.

= Deterministic reproduction =

A development-only two-request probe used a unique harmless PHP file, not WordPress's real .maintenance file.

Environment:

{{{
PHP 8.3.32 FPM
opcache.enable_file_override = 1
opcache.validate_timestamps = 1
opcache.revalidate_freq = 2
opcache.file_update_protection = 2
realpath_cache_size = 4096K
realpath_cache_ttl = 120
}}}

Steps:

1. Request A creates and requires the temporary PHP file so it is compiled in shared OPcache.
2. Request A calls Request B.
3. Request B deletes the file and confirms deletion.
4. Request A checks and opens the deleted path.

The initial run and five confirmation runs all produced:

{{{
opcache_is_script_cached before deletion = true
second request deleted the file = true
file_exists after deletion = true
fopen after deletion = false
file_exists after clearstatcache = true
opcache_invalidate = true
file_exists after opcache_invalidate = false
}}}

Twenty control runs that warmed only file_exists(), without compiling the file, did not produce a stale result.

This demonstrates the exact contradiction required for the Core failure: file_exists() passes, but the subsequent open/require cannot find the file. clearstatcache() is not sufficient for this OPcache configuration.

= Real WordPress update confirmation =

A manual Core update from WordPress 7.0.4 to 7.1 was run on the same development environment with a temporary fatal-error-handler.php drop-in that recorded state immediately before delegating to Core's unmodified WP_Fatal_Error_Handler.

Observed:

* The initiating response stopped after “Unpacking the update...”.
* A fresh administration request confirmed WordPress 7.1 was installed successfully.
* Immediately before Core's shutdown handler, file_exists(.maintenance) returned true and opcache_is_script_cached(.maintenance) returned true.
* error_get_last() contained only an unrelated E_WARNING from plugin enumeration, not a fatal error.
* Core's subsequent maintenance check logged require(<wordpress-root>/.maintenance): Failed to open stream: No such file or directory in wp-includes/load.php.
* No earlier fatal error, memory exhaustion, or execution timeout was captured.

The same missing-file warning/fatal has also followed successful theme and translation updates. In several historical incidents, “Disabling Maintenance mode”, the successful update record, and the missing-file failure have the same timestamp.

Increasing PHP/WP memory limits and max_execution_time did not prevent the issue. Equivalent updates on a comparison host have not reproduced it so far.

= Expected result =

A deleted maintenance file should not interrupt shutdown after a successful update. wp_is_maintenance_mode() should defensively handle the file disappearing or becoming unreadable between its existence check and load.

A host-level mitigation is to disable opcache.enable_file_override, but Core should not assume that the result of file_exists() guarantees that a later require will succeed.

= Related reports =

#62176 describes related maintenance-file races in the automatic updater, but focuses on the file being empty while it is created and on fatal-error loopback detection. This report concerns a deleted file that remains visible to file_exists() through OPcache and can occur during an otherwise successful manual update.

An independent support report contains the same missing .maintenance shutdown stack:
https://de.wordpress.org/support/topic/plugin-aktualisierung-fehlgeschlagen-successtruedata/
"	defect (bug)	new	normal	7.2	Upgrade/Install	7.0.4	normal		has-test-info has-patch has-unit-tests		
