Skip to content

[Generic Execution Engine] PermissionService starts the session in its constructor, closing the EntityManager during streamed responses #616

Closed

Description

@jremmurd

Affected Version

2026.3

Affected capability

Core

Steps to reproduce

Verified on pimcore/pimcore v2026.3.0; PermissionService is unchanged on 2026.x.

  1. Have a frontend controller that returns a StreamedResponse (for example server-sent events) and writes a Doctrine entity while streaming, after the first chunk has been flushed. The entity has nothing to do with the execution engine.
  2. Call it with any session cookie on the request (Cookie: PHPSESSID=<anything>), e.g. from a browser that is logged into the Pimcore admin on the same host.
  3. The first Doctrine preUpdate of the request makes the ContainerAwareEventManager build its preUpdate listeners, among them GenericExecutionEngineBundle\EventSubscriber\JobRunStatusChangeSubscriber.

The same request without the session cookie works.

Actual Behavior

Building JobRunStatusChangeSubscriber builds JobRunRepository, which builds GenericExecutionEngineBundle\Security\PermissionService. Its constructor calls Pimcore\Tool\Authentication::authenticateSession(), which reads _security_pimcore_admin from the session. With a previous session on the request this starts the PHP session. The response has already sent its headers, so NativeSessionStorage::start() throws:

RuntimeException: Failed to start the session because headers have already been sent by "..." at line ...

The exception is thrown inside EntityManager::flush(), so Doctrine closes the EntityManager and every later write of the request fails with The EntityManager is closed.

Relevant frames:

Symfony\Component\HttpFoundation\Session\Session->get('_security_pimcore_admin')
Pimcore\Tool\Authentication::authenticateSession()
Pimcore\Bundle\GenericExecutionEngineBundle\Security\PermissionService->__construct()
getJobRunRepositoryInterfaceService::do()
getJobRunStatusChangeSubscriberService::do()
Symfony\Bridge\Doctrine\ContainerAwareEventManager->initializeListeners('preUpdate')
Doctrine\ORM\UnitOfWork->executeUpdates()
Doctrine\ORM\EntityManager->flush()

Expected Behavior

Writing an unrelated entity must not touch the session. JobRunStatusChangeSubscriber listens to preUpdate of every entity and only filters for JobRun inside, so its dependency chain, and with it the session read, is built in any request that updates any entity.

Suggested fix: resolve the user in PermissionService on first use instead of in the constructor (a lazy getter used by isAllowedToSeeJobRuns() / isAllowedToSeeAllJobRuns() and the other checks). Alternatively, or additionally, register JobRunStatusChangeSubscriber as an entity listener for JobRun only (#[AsEntityListener(entity: JobRun::class)]), so it is not built for other entities at all.

Related: #486 (same subscriber closing the EntityManager inside preUpdate, different trigger).

Activity

  1. updated Affected capability Core and Platform Version 2026.3 on Oct 13, 2026
  2. added theissue type on Oct 14, 2026
  3. self-assigned this
    on Oct 16, 2026
  4. added this to the 2026.3 milestone on Oct 16, 2026
  5. kingjia90 commented on Oct 16, 2026

    @kingjia90
    Contributor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Fields

Platform Version

2026.3

Affected capability

Core

Galaxy

None yet

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions