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::wait".
What it means
wait($callback) can only deliver output through a callback if the process pipes have read support — either a callback was given to start() or enableOutput() was called. Otherwise there is nothing to read and calling wait with a callback would hang or lose output, so a LogicException is thrown (after stopping the process) telling you which of the two setup steps is missing.
Solutions
- Pass the callback to start(): $process->start($callback); then call wait().
- Call $process->enableOutput() (or Process::enableOutputByDefault()) before start, then wait($callback).
- If you don't need output, call wait() without a callback and use getOutput() afterwards.
- Unify on one style: either run($callback) or start()/wait($callback) with read support enabled.
Example fix
// before
$process->start();
$process->wait(function ($type, $buffer) { echo $buffer; }); // LogicException
// after
$process->start(function ($type, $buffer) { echo $buffer; });
$process->wait(); Defensive patterns
Strategy: validation
Validate before calling
if (!$this->readOutput && null === $callback) {
// no read support: either enable output or skip callback use
}
$process->enableOutput();
$process->start();
$process->wait($callback); Try / catch
try {
$process->wait($callback);
} catch (\LogicException $e) {
if (str_contains($e->getMessage(), 'Pass the callback to the "Process::start"')) {
// restart flow: pass callback to start() or enableOutput() first
}
throw $e;
} Prevention
- Pick one style: run($callback) or start($callback)/wait(); do not move the callback between calls.
- Call enableOutput() (or Process::enableOutputByDefault()) whenever output consumption is planned.
- Note wait($callback) also stops the process on this error — restart if you recover.
- Add a static-analysis/lint rule or helper wrapper that requires a callback on start when wait will use one.
When it happens
Trigger: Calling $process->wait($someCallback) when start() was called without a callback and enableOutput()/enableOutputByDefault() was never invoked; the pipes layer reports haveReadSupport() === false.
Common situations: Mixing the run($callback) style with the start()/wait() style; starting a fire-and-forget process then later deciding to consume output; refactors that moved the callback from start() to wait().
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/225343cf7ae2339f.
Report an issue: GitHub.
Appendix: source
Thrown at Process.php:478
* @param (callable('out'|'err', string):void)|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return int The exitcode of the process
*
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/
public function wait(?callable $callback = null): int
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (null !== $callback) {
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::wait".');
}
$this->callback = $this->buildCallback($callback);
}
do {
$this->checkTimeout();
$running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen());
$this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
} while ($running);
while ($this->isRunning()) {
$this->checkTimeout();
usleep(1000);
}
if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
throw new ProcessSignaledException($this);
}View on GitHub (pinned to 99b85026db)