flarum/framework · info
Halt signal received, killing to restart.
Error message
Halt signal received, killing to restart.
What it means
The realtime websocket server (ServeCommand) polls the cache every 10 seconds for the HaltCommand KEY; when present, it warns 'Halt signal received, killing to restart.', clears the key, and stops the ReactPHP loop so the process exits (typically for a supervisor/systemd to restart it with fresh code/config).
Solutions
- No action needed — this is expected restart behavior
- Ensure a process supervisor (systemd, pm2, Docker restart policy) restarts the server after exit
- If it loops unexpectedly, inspect who is running `php flarum halt` or what writes HaltCommand::KEY to the cache
Defensive patterns
Strategy: fallback
Validate before calling
# ensure a supervisor restarts the websocket server # systemd: Restart=always under [Service]
Prevention
- Always run websocket:serve under a supervisor with auto-restart
- Coordinate `flarum halt` calls with deployment tooling
- Monitor the process and the halt cache key when diagnosing restart loops
When it happens
Trigger: Running `php flarum websocket:serve` (or the serve command) while `php flarum halt` has been executed elsewhere, setting HaltCommand::KEY in the cache.
Common situations: Deployments or config changes that call `flarum halt` to signal the long-running websocket process to recycle; admin actions that require a restart.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- One or more extensions have changed, killing to restart.
- Paths are not possible in websocket connections.
- Invalid auth signature provided.
- $errno, $errstr, $errfile:$errline
- [$errno] $errstr in $errfile:$errline
AI-assisted analysis of flarum/framework@4b939f6853 (2026-09-15).
Data as JSON: /api/errors/a06cefba6bcf5f35.
Report an issue: GitHub.
Appendix: source
Thrown at extensions/realtime/src/Websocket/Console/ServeCommand.php:180
'data' => ['revision' => $token],
];
$manager->getChannels()->then(function (array $channels) use ($payload) {
foreach ($channels as $name => $channel) {
if ($name === 'public' || str_starts_with($name, 'private-user=')) {
$payload->channel = $name;
$channel->broadcast($payload);
}
}
});
});
}
protected function restartOnCachedSignal(LoopInterface $loop, Repository $cache): void
{
$loop->addPeriodicTimer(10, function (TimerInterface $timer) use ($loop, $cache) {
if ($cache->has($key = HaltCommand::KEY)) {
$this->warn('Halt signal received, killing to restart.');
$cache->forget($key);
$loop->stop();
}
});
}
protected function restartOnExtensionChanges(LoopInterface $loop): void
{
$enabled = null;
$loop->addPeriodicTimer(10, function (TimerInterface $timer) use ($loop, &$enabled) {
$app = $this->getLaravel();
// If a Redis-backed settings cache is bound (e.g. fof/redis), use the settings
// repository which will read from Redis. Otherwise fall back to a direct DB query,
// because the default MemoryCacheSettingsRepository is a per-process in-memory cacheView on GitHub (pinned to 4b939f6853)