VulCode
Blog

Pelican Panel: updating a plugin no longer crashes

Updating a plugin in the Pelican panel died with \"Call to a member function query() on null\", because the code refreshed a model whose connection it had just reset. It now loads a fresh record.

DualFroz - VulCode CEODualFroz - VulCode CEO1 min read
Pelican Panel: updating a plugin no longer crashes - DualFroz - VulCode CEO
Title in the repository
Fix plugin update crashing after the rows are refreshed
Merged
25 Sep 2026
Written up here
6 Oct 2026

The note was written 11 days after the change was merged.

Pelican Panel
Technologies:PHPLaravel

What was broken

Pelican is a free, open source panel for managing game servers, built on Laravel and Docker. Its list of plugins is a model backed by the Sushi library, which keeps rows in memory instead of a regular table.

Since an earlier change, updatePlugin() called Plugin::refreshRows() after downloading the new version, and then $plugin->refresh(). The catch is that refreshRows() resets the Sushi connection, so refresh() on the old model instance died with Call to a member function query() on null (issue #2588). Updating a plugin from the panel simply crashed.

What we changed

Instead of refreshing the old instance, the code loads the record again with Plugin::findOrFail(), exactly as the plugin jobs in the same project already do:

php
Plugin::refreshRows();
$plugin = Plugin::findOrFail($plugin->id);

updatePlugin() now returns the updated plugin, and the API controller sends that fresh object back to the client instead of a stale copy from before the update.

How we tested it

We added an integration test for a successful update: a faked download of a package with a new version, the update itself and a check of the plugin's state afterwards. On main before the fix, the test fails with exactly the error from the issue. The test also cleans up the Sushi rows after itself, so removed plugins do not leak into later tests.

The result

The change was merged on 25 September 2026. Plugin updates in Pelican work from the panel and through the API.

A game server management panel on Laravel and Docker, a fork of Pterodactyl.

Language
PHP
GitHub stars
2,375

The same case before and after the change

On the example of the note: what came out before the fix and what comes out now.

A plugin update in the panel
updatePlugin()

After downloading the new version, the code refreshes the rows of the Plugin model, which the Sushi library keeps in memory.

Before the change
Plugin::refreshRows();
$plugin->refresh();

refreshRows() resets the Sushi connection, so refresh() on the old instance failed with query() on null (issue #2588).

After the change
Plugin::refreshRows();
$plugin = Plugin::findOrFail($plugin->id);

The record is loaded again, updatePlugin() returns the updated plugin, and the API sends back the fresh object.

What it taught us

An update that fails halfway must not leave the system worse off than before. We check the same in the panel updates we build.

From the change in pull request #2598; on the main branch before the fix, the integration test fails with the same error.

Questions about change #2598

Where the change is, how we tested it and who merged it.