From 24648d4b2079f681b4a9c710cdb8a537cd42dfa1 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 28 Oct 2026 22:13:27 +0100 Subject: [PATCH] fix(docker-cleanup): skip broadcast for executions of deleted servers Co-Authored-By: Claude Opus 5.5 --- app/Events/DockerCleanupDone.php | 20 ++++++++++++++++---- tests/Feature/DockerCleanupJobTest.php | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/Events/DockerCleanupDone.php b/app/Events/DockerCleanupDone.php index f229c66451..71eb80e2f8 100644 --- a/app/Events/DockerCleanupDone.php +++ b/app/Events/DockerCleanupDone.php @@ -13,13 +13,25 @@ class DockerCleanupDone implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; - public function __construct(public DockerCleanupExecution $execution) {} + public ?int $teamId; + public function __construct(public DockerCleanupExecution $execution) + { + $this->teamId = $execution->server?->team_id; + } + + /** + * A deleted server has no team to tell, so nothing is broadcast. + * + * @return array + */ public function broadcastOn(): array { - return [ - new PrivateChannel('team.'.$this->execution->server->team->id), - ]; + if (is_null($this->teamId)) { + return []; + } + + return [new PrivateChannel("team.{$this->teamId}")]; } /** diff --git a/tests/Feature/DockerCleanupJobTest.php b/tests/Feature/DockerCleanupJobTest.php index a12808c15b..5d0b30b571 100644 --- a/tests/Feature/DockerCleanupJobTest.php +++ b/tests/Feature/DockerCleanupJobTest.php @@ -867,3 +867,19 @@ it('keeps the images of an application while it deploys, because its new images ->and($removals->first())->toContain("'{$idle->uuid}:old'") ->not->toContain($deploying->uuid); })->with(['queued', 'in_progress']); + +it('fails an interrupted cleanup of a deleted server without breaking its broadcast', function () { + Carbon::setTestNow(Carbon::create(2026, 10, 28, 12, 0, 0, 'UTC')); + Queue::fake(); + Notification::fake(); + $server = dockerCleanupReachableServer(); + $interrupted = DockerCleanupExecution::create(['server_id' => $server->id]); + $server->delete(); + Carbon::setTestNow(now()->addSeconds(CleanupDocker::overlapLockExpiresAfter() + 1)); + + DockerCleanupJob::failInterruptedExecutions(); + + expect($interrupted->fresh()->status)->toBe('failed'); + Queue::assertPushed(BroadcastEvent::class, fn (BroadcastEvent $job) => $job->event instanceof DockerCleanupDone + && $job->event->broadcastOn() === []); +});