EnvironmentService.DeleteEnvironment checks for referencing sessions and deletes the environment in two separate calls: CountSessionsByEnvironment, then (inside uow.Run) environments.DeleteEnvironment. Nothing locks across the gap between them.
A CreateSession against that environment can land in the gap: the count already read 0, so the delete proceeds unconditionally and the environment is gone while a session still references it.
Sessions never persist the capability Requirements they were created with, they're re-derived from the live Environment at each placement pass (controlapp/scheduler.go, queuedEnvironment/candidatesFor). Once the environment resolves to nil, that session's capability filtering and setup/init hooks silently drop rather than erroring.
Confirmed in both store backends:
pgstore: DeleteEnvironment issues a plain DELETE with no NOT EXISTS subquery tying it to the count.
memstore: CountSessionsByEnvironment and DeleteEnvironment each take and release m.mu separately.
TestDeleteEnvironmentGuard only exercises the sequential case (session already present when the count runs), so nothing currently pins the race.
I have a fix (fold the check into one atomic repository call) with a regression test that reproduces the race through the real HTTP handler. PR to follow.
EnvironmentService.DeleteEnvironmentchecks for referencing sessions and deletes the environment in two separate calls:CountSessionsByEnvironment, then (insideuow.Run)environments.DeleteEnvironment. Nothing locks across the gap between them.A
CreateSessionagainst that environment can land in the gap: the count already read 0, so the delete proceeds unconditionally and the environment is gone while a session still references it.Sessions never persist the capability
Requirementsthey were created with, they're re-derived from the liveEnvironmentat each placement pass (controlapp/scheduler.go,queuedEnvironment/candidatesFor). Once the environment resolves to nil, that session's capability filtering and setup/init hooks silently drop rather than erroring.Confirmed in both store backends:
pgstore:DeleteEnvironmentissues a plainDELETEwith noNOT EXISTSsubquery tying it to the count.memstore:CountSessionsByEnvironmentandDeleteEnvironmenteach take and releasem.museparately.TestDeleteEnvironmentGuardonly exercises the sequential case (session already present when the count runs), so nothing currently pins the race.I have a fix (fold the check into one atomic repository call) with a regression test that reproduces the race through the real HTTP handler. PR to follow.