symfony/process · error · LogicException

Process must be terminated before calling

Error message

Process must be terminated before calling "%s()".

What it means

requireProcessIsTerminated() guards post-mortem accessors (hasBeenSignaled, getTermSignal, hasBeenStopped, getStopSignal). These reflect final exit data that only exists after the process terminated; calling them earlier throws a LogicException.

Solutions

  1. Call wait() and ensure the process finished before reading signal info
  2. Check isTerminated() before calling these accessors
  3. Use callbacks (e.g. wait's callable) that run only at termination for final reporting

Example fix

// before
$process->start();
$sig = $process->getTermSignal();
// after
$process->run();
if ($process->isTerminated()) {
    $sig = $process->hasBeenSignaled() ? $process->getTermSignal() : null;
}
Defensive patterns

Strategy: validation

Validate before calling

if ($process->isTerminated()) {
    $signaled = $process->hasBeenSignaled();
    $termSignal = $signaled ? $process->getTermSignal() : null;
}

Type guard

function exitSignal(Symfony\Component\Process\Process $p): ?int { return $p->isTerminated() && $p->hasBeenSignaled() ? $p->getTermSignal() : null; }

Try / catch

try { $sig = $process->getTermSignal(); } catch (LogicException $e) { $sig = null; }

Prevention

When it happens

Trigger: Calling $process->getTermSignal() or hasBeenSignaled() while the process is still running, e.g. inside progress polling before wait() returned.

Common situations: Checking exit signal information in a loop before completion, calling signal accessors right after start(), integrating with callbacks that run before termination.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of symfony/process@99b85026db (2026-09-14). Data as JSON: /api/errors/8d042b8115fc6eb9. Report an issue: GitHub.

Appendix: source

Thrown at Process.php:1670

     *
     * @throws LogicException if the process has not run
     */
    private function requireProcessIsStarted(string $functionName): void
    {
        if (!$this->isStarted()) {
            throw new LogicException(\sprintf('Process must be started before calling "%s()".', $functionName));
        }
    }

    /**
     * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
     *
     * @throws LogicException if the process is not yet terminated
     */
    private function requireProcessIsTerminated(string $functionName): void
    {
        if (!$this->isTerminated()) {
            throw new LogicException(\sprintf('Process must be terminated before calling "%s()".', $functionName));
        }
    }

    /**
     * Escapes a string to be used as a shell argument.
     */
    private function escapeArgument(?string $argument): string
    {
        if ('' === $argument || null === $argument) {
            return '""';
        }
        if ('\\' !== \DIRECTORY_SEPARATOR) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }
        if (str_contains($argument, "\0")) {
            $argument = str_replace("\0", '?', $argument);
        }
        if (!preg_match('/[()%!^"<>&|\s[\]=;*?\'$]/', $argument)) {

View on GitHub (pinned to 99b85026db)