Summary
Geocoding::search() treats the ability's return value as an array without an is_wp_error() guard. executeGeocodeSearch() is declared array|\WP_Error, so every WP_Error return path fatals instead of becoming a clean 4xx.
This is live on production right now and is user-facing: the city/venue autocomplete fires per keystroke, so every user typing a location triggers a 500 at 1–2 characters before the query becomes valid at 3.
Reproduction (production, verified)
curl -o /dev/null -w "%{http_code}" \
"https://events.extrachill.com/wp-json/datamachine/v1/events/geocode/search?query=ab"
→ 500
curl "…/events/geocode/search?query=charleston"
→ 200 (valid path works fine)
Root cause
inc/Api/Controllers/Geocoding.php:35
$result = $abilities->executeGeocodeSearch( array( 'query' => ... ) );
if ( ! empty( $result['error'] ) ) { // ← line 35: $result may be WP_Error
inc/Abilities/GeocodingAbilities.php:196 declares:
public function executeGeocodeSearch( array $input ): array|\WP_Error
and returns WP_Error on two paths:
- L199-201 —
invalid_query, query shorter than 3 characters (already carries status => 400)
- L214-216 — passthrough of a
NominatimClient::searchAddress() transport error
Neither is handled. Cannot use object of type WP_Error as array is thrown at line 35.
Secondary: the $result['error'] check itself is dead code — the success path returns array( 'success' => true, 'results' => ... ) and never sets an error key. The controller is checking for an error shape the ability does not emit, while ignoring the error shape it does.
Evidence from production
17 fatals in the live debug.log since rotation at 00:00 UTC 2026-09-04, all with the identical stack:
PHP Fatal error: Uncaught Error: Cannot use object of type WP_Error as array in
.../data-machine-events/inc/Api/Controllers/Geocoding.php:35
#0 wp-includes/rest-api/class-wp-rest-server.php(1293):
DataMachineEvents\Api\Controllers\Geocoding->search()
Note this signature also appears network-wide in the composed health read across all 10 subsites, because it lands in the shared debug.log.
Expected
The WP_Error should be returned directly — it already carries the correct status (400), so REST renders a proper error response.
$result = $abilities->executeGeocodeSearch( array( 'query' => ... ) );
if ( is_wp_error( $result ) ) {
return $result;
}
return rest_ensure_response( $result );
Acceptance
?query=ab returns 400 with a JSON error body, not 500
?query=charleston still returns 200 with the results payload
- No
Cannot use object of type WP_Error as array entries in debug.log after deploy
- A transport-layer Nominatim failure surfaces as its own error response rather than a fatal
Layer note
The fix belongs in the controller, which is the thin REST adapter. The ability's array|\WP_Error contract is correct and should not be changed to accommodate the caller — the adapter is the layer that failed to honor the declared contract.
Summary
Geocoding::search()treats the ability's return value as an array without anis_wp_error()guard.executeGeocodeSearch()is declaredarray|\WP_Error, so everyWP_Errorreturn path fatals instead of becoming a clean 4xx.This is live on production right now and is user-facing: the city/venue autocomplete fires per keystroke, so every user typing a location triggers a 500 at 1–2 characters before the query becomes valid at 3.
Reproduction (production, verified)
Root cause
inc/Api/Controllers/Geocoding.php:35inc/Abilities/GeocodingAbilities.php:196declares:and returns
WP_Erroron two paths:invalid_query, query shorter than 3 characters (already carriesstatus => 400)NominatimClient::searchAddress()transport errorNeither is handled.
Cannot use object of type WP_Error as arrayis thrown at line 35.Secondary: the
$result['error']check itself is dead code — the success path returnsarray( 'success' => true, 'results' => ... )and never sets anerrorkey. The controller is checking for an error shape the ability does not emit, while ignoring the error shape it does.Evidence from production
17 fatals in the live
debug.logsince rotation at 00:00 UTC 2026-09-04, all with the identical stack:Note this signature also appears network-wide in the composed health read across all 10 subsites, because it lands in the shared debug.log.
Expected
The
WP_Errorshould be returned directly — it already carries the correctstatus(400), so REST renders a proper error response.Acceptance
?query=abreturns 400 with a JSON error body, not 500?query=charlestonstill returns 200 with the results payloadCannot use object of type WP_Error as arrayentries indebug.logafter deployLayer note
The fix belongs in the controller, which is the thin REST adapter. The ability's
array|\WP_Errorcontract is correct and should not be changed to accommodate the caller — the adapter is the layer that failed to honor the declared contract.