coollabsio/coolify · error · RuntimeException

No Vultr token found for the server team.

Error message

No Vultr token found for the server team.

What it means

Thrown by DeleteServer::deleteFromVultrById() when a deletion is requested with deleteFromVultr=true but no CloudProviderToken row with provider='vultr' exists for the server's team. The action first tries the explicitly passed cloudProviderTokenId, then falls back to any Vultr token owned by the team; if neither lookup matches, it throws RuntimeException. Unlike the Hetzner path (which silently returns), the catch block logs and re-throws, aborting the entire DeleteServer action — the server row is not deleted from Coolify.

Source

Thrown at app/Actions/Server/DeleteServer.php:125

    {
        try {
            $token = null;

            if ($cloudProviderTokenId) {
                $token = CloudProviderToken::where('id', $cloudProviderTokenId)
                    ->where('team_id', $teamId)
                    ->where('provider', 'vultr')
                    ->first();
            }

            if (! $token) {
                $token = CloudProviderToken::where('team_id', $teamId)
                    ->where('provider', 'vultr')
                    ->first();
            }

            if (! $token) {
                throw new \RuntimeException('No Vultr token found for the server team.');
            }

            $vultrService = new VultrService($token->token);
            $vultrService->deleteInstance($vultrInstanceId);

            logger()->debug('Deleted server from Vultr', [
                'vultr_instance_id' => $vultrInstanceId,
                'team_id' => $teamId,
            ]);
        } catch (\Throwable $e) {
            logger()->error('Failed to delete server from Vultr', [
                'error' => $e->getMessage(),
                'vultr_instance_id' => $vultrInstanceId,
                'team_id' => $teamId,
            ]);

            throw $e;
        }

View on GitHub (pinned to 70b9acc424)

Solutions

  1. Re-add a Vultr provider token for the server's team so the fallback lookup (CloudProviderToken where team_id=server team, provider='vultr') succeeds.
  2. Pass an explicit cloudProviderTokenId of a valid Vultr token when dispatching DeleteServer.
  3. If the Vultr instance was already destroyed manually, delete the server without deleteFromVultr so no token lookup happens.
  4. If calling programmatically and you want best-effort deletion, catch the RuntimeException like the Hetzner path does (log + notify) instead of letting it abort.

Example fix

// before
DeleteServer::run($serverId, deleteFromVultr: true, vultrInstanceId: $server->vultr_instance_id);
// throws RuntimeException when team has no Vultr token

// after
$token = CloudProviderToken::where('team_id', $server->team_id)
    ->where('provider', 'vultr')
    ->first();
if ($token) {
    DeleteServer::run($serverId, deleteFromVultr: true,
        vultrInstanceId: $server->vultr_instance_id,
        cloudProviderTokenId: $token->id);
} else {
    DeleteServer::run($serverId); // skip provider deletion, or surface a warning
}
Defensive patterns

Strategy: validation

Validate before calling

$hasVultrToken = \App\Models\CloudProviderToken::query()
    ->where('team_id', $server->team_id)
    ->where('provider', 'vultr')
    ->exists();
if (! $hasVultrToken) {
    // surface a config error to the user instead of dispatching a doomed deletion
    return back()->withErrors(['server' => 'Add a Vultr token for this team before deleting the server from Vultr.']);
}
// safe: fallback lookup in DeleteServer will find the token
\App\Actions\Server\DeleteServer::run($server->id, deleteFromVultr: true, vultrInstanceId: $server->vultr_instance_id);

Type guard

function teamHasVultrToken(int $teamId): bool
{
    return \App\Models\CloudProviderToken::query()
        ->where('team_id', $teamId)
        ->where('provider', 'vultr')
        ->exists();
}

Try / catch

try {
    DeleteServer::run($serverId, deleteFromVultr: true, vultrInstanceId: $instanceId);
} catch (\RuntimeException $e) {
    if (str_contains($e->getMessage(), 'No Vultr token found')) {
        // keep the server row, notify the team to re-link a token, do not retry blindly
        report($e);
    } else {
        throw $e; // provider API failure — different handling
    }
}

Prevention

When it happens

Trigger: Calling DeleteServer::run with deleteFromVultr=true (or deleting a Server that has vultr_instance_id with provider deletion enabled) while: the team's Vultr token row was deleted, cloudProviderTokenId points at a non-Vultr token or a token of another team, or the server's team_id no longer matches the team owning the token.

Common situations: Vultr API token removed or rotated after the server was created; server moved between teams; automation dispatching provider deletion without passing cloudProviderTokenId; token deleted as part of cleanup while Vultr-linked servers still exist.

Related errors


AI-assisted analysis of coollabsio/coolify@70b9acc424 (2026-08-17). Data as JSON: /api/errors/08a8c91be2338592. Report an issue: GitHub.