symfony/process · error · LogicException
Pass the callback to the "Process::start" method or call…
Error message
Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".
What it means
waitUntil($callback) polls the running process until the callback returns true, which requires read support on the pipes. If start() was called without a callback and output was never enabled, waitUntil cannot read output for its callback and throws LogicException (stopping the process first), mirroring the wait() restriction.
Solutions
- Call $process->enableOutput() before start() (or Process::enableOutputByDefault() globally), then start()/waitUntil().
- Pass a callback to start() to implicitly enable read support, then use waitUntil().
- If output polling is not needed, replace waitUntil with wait() plus a status check on completion.
- Ensure requireProcessIsStarted passes too — waitUntil only works on a started process.
Example fix
// before $process->start(); $process->waitUntil(fn ($type, $buffer) => str_contains($buffer, 'Ready')); // LogicException // after $process->enableOutput(); $process->start(); $process->waitUntil(fn ($type, $buffer) => str_contains($buffer, 'Ready'));
Defensive patterns
Strategy: validation
Validate before calling
$process->enableOutput(); $process->start(); // now safe: $process->waitUntil(fn (string $type, string $buffer): bool => str_contains($buffer, 'Ready'));
Try / catch
try {
$process->waitUntil($predicate);
} catch (\LogicException $e) {
if (str_contains($e->getMessage(), '"Process::waitUntil"')) {
// restructure: enableOutput() before start, or start($callback)
}
throw $e;
} Prevention
- Whenever using waitUntil for output-based conditions, always enable output before start().
- Remember waitUntil also requires the process to be started (requireProcessIsStarted).
- Centralize process startup in a helper that enables output and passes a callback so wait/waitUntil are always legal.
- On this error the process is stopped (0s) — restart it when recovering.
When it happens
Trigger: Calling $process->waitUntil($fn) after start() with no callback and without enableOutput()/enableOutputByDefault(); haveReadSupport() returns false so the exception is raised before any polling.
Common situations: Condition-based waiting (e.g. 'wait until the server prints Ready') on a process started without output collection; migrating from wait() code that had read support to waitUntil() on a start()-without-callback flow.
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
- Pass the callback to the "Process::start" method or call…
- " " yielded a value of type " ", but only scalars and…
- A temporary file could not be opened to write the process…
- Process is already running.
- The provided cwd " " does not exist.
AI-assisted analysis of symfony/process@99b85026db (2026-09-14).
Data as JSON: /api/errors/6db09d7f871efd32.
Report an issue: GitHub.
Appendix: source
Thrown at Process.php:524
* It allows to have feedback from the independent process during execution.
*
* @param-immediately-invoked-callable $callback
*
* @param (callable('out'|'err', string):bool)|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @throws RuntimeException When process timed out
* @throws LogicException When process is not yet started
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function waitUntil(callable $callback): bool
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
}
$callback = $this->buildCallback($callback);
$ready = false;
while (true) {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
foreach ($output as $type => $data) {
if (3 !== $type) {
$ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
if ($ready) {
return true;View on GitHub (pinned to 99b85026db)