Skip to content

When an app fails: fault isolation and recovery

5 min read

Since Basehim 1.2.71 one broken app can't take a site down. When an app can't load or start, Basehim switches that app off and keeps everything else running. The administrator gets a report and a safe way to turn the app back on once it's fixed.

This page explains what happens, what you as an app author should know, and where the limits are.

Why this exists

Active apps are loaded on every request: page views, the admin, the API and MCP. Before 1.2.71 some mistakes in an app weren't exceptions at all, but engine errors that stop PHP before any error handling can run. Two examples:

  • a method that clashes with one in the App base class, such as an app's own private function schema() next to core's protected function schema(string $sql): void;
  • a class name core already uses.

The result was a 500 on every page, including the admin, and the only fix was editing the database or deleting the app's folder.

What happens when an app fails

The app…When it's caughtWhat visitors seeWhat happens to the app
has a syntax errorbefore any of its code is loadednothing: the page rendersswitched off, report saved
declares a method its parent doesn't allow (narrower visibility, an incompatible signature, static vs. instance), reuses a core class name, or extends a class that doesn't existbefore loadingnothingswitched off
throws from its constructor or boot() (any exception, a TypeError, a missing class)while loading, in the same requestnothing: the page renders without the appits routes, hooks, menu items, AJAX actions, widgets and content types are removed; switched off
hits an engine error the checks couldn't see (for example, a file edited in the last minute)when PHP stopsthat one request answers 503 and reloads itself after a secondswitched off; the next request runs without it
runs out of memory or timewhen PHP stopsthat request failscounted; switched off on the third time within ten minutes
fails because the database is unreachablewhile loadingwhatever core shows for a database outagenot switched off; skipped for that request only

"Switched off" means three things:

  • the app's status becomes error;
  • a report is written to storage/app-health/{slug}.json;
  • the failure is logged once, in the core log and in the app's own log.

The app's files, settings, tables and data are never touched. If the database can't be written at that moment, the report file alone keeps the app off, and the status is written once the database is back.

Checking code before it runs

Basehim checks an app in three steps:

  1. The manifest: the namespace, entry class and source folder make sense.
  2. Declarations, without running any code. Every PHP file is read with PHP's tokenizer, which also finds syntax errors. Each class is compared with its parent: core classes through reflection, the app's own classes through their source. Only what PHP itself would reject is reported, so a healthy app is never switched off by mistake.
  3. An isolated load test. A separate PHP process loads the app's classes against this version of core, with no database credentials and no .env. If PHP refuses a class, only that process ends. The test needs the PHP command line on the server. Basehim finds it on cPanel, CloudLinux and most Linux hosts; set PHP_CLI_BINARY in .env to point at another one. Where there is none, steps 1 and 2 still run, and the report says the load test was skipped.

The checks run at these moments:

  • when an app is installed or updated (upload, marketplace or upgrade);
  • every time it's activated;
  • when an administrator clicks Check again;
  • before an active app is loaded, whenever its files, its app.json, core or the PHP version have changed since it last passed.

Results are remembered by a fingerprint of those inputs, so an ordinary request does no checking at all. After a core update every active app is checked once, on the first request. The load tests run in parallel and take about 80 ms each.

The administrator's side

  • Apps screen: the app's card turns red, "Disabled — error", with the reason, the time, View report and Retry.
  • Banner: every admin page shows a banner to people who can manage apps, until the app is dealt with.
  • The report (/admin/apps/{slug}/health) shows:
    • what failed, when, at which stage and where;
    • the request ID and the call stack;
    • whether the database was updated;
    • the last full check, the app's routes, and earlier failures.
    The report is drawn by core, never by the app, and needs manage_apps. Opening one of the app's own admin screens leads there too.
  • Check and reactivate runs the checks again and turns the app on only if they pass. If it then fails while starting, it is switched off again, and the earlier failure stays in its history.
  • Keep it off deactivates it normally. Nothing requires editing the database or deleting files.

For app authors

  • Don't reuse the names of the base class's methods, such as get, post, route, table, schema, db, log, view, icon or settings, unless you mean to override them with a compatible signature. Core itself no longer adds methods to the base class, for the same reason.
  • A boot() that throws switches your app off. Before 1.2.71 the throw was only logged, and the app kept running half-registered. Catch what you expect to fail, such as a remote service or an optional file, and let real defects surface.
  • Keep class files free of code that runs when the file is included. The isolated load test includes them.
  • Before you ship, open Apps → your app → Report → Check again on a test site.

Limits

  • An engine error still ends the request it happens in. PHP has no way to continue after one. The checks exist so that this happens before the code is loaded. When it does slip through, it costs one self-reloading 503.
  • Errors while serving a page (in a controller your app loads later) switch the app off only when they are declaration errors. Other errors give that route a 500 and aren't blamed on the app.
  • This is fault isolation, not a sandbox. An app runs with core's rights. Install apps you trust, and review their permissions.
  • On a site with several web servers, reports are kept per server, like the rest of storage/.

Keep building

More guides, or jump straight into the API reference.

Discussion

Leave a comment