agalwood/Motrix · error · AppError
EngineStartFailed
EngineStartFailed
Error message
Engine recovery is unavailable before the first start attempt
What it means
Thrown as an AppError (code EngineStartFailed) by EngineSupervisor.recover when this.binaryPath is falsy — meaning start() has never been successfully called (or was never attempted), so the supervisor has no binary path to recover with. Recovery actions (retry, force-terminate, switch-port, restore-default-port) all require a known binary path and prior start context. This is a lifecycle/ordering guard.
Source
Thrown at src/core/engine/engine-supervisor.ts:537
isCurrent: defaultRpcIsCurrent,
available: defaultRpcAvailable,
process: defaultRpcProcess,
canRestore:
!defaultRpcIsCurrent &&
(defaultRpcAvailable || defaultRpcRequiresTermination),
requiresTermination: defaultRpcRequiresTermination,
},
suggestedRpcPort,
canRetry,
canForceTerminate,
canSwitchPort: canSwitchPort && suggestedRpcPort !== null,
recommendation,
}
}
async recover(request: EngineRecoveryRequest): Promise<EngineRecoveryResult> {
if (!this.binaryPath) {
throw new AppError(
ErrorCode.EngineStartFailed,
'Engine recovery is unavailable before the first start attempt'
)
}
const previousRpcPort = this.settingsManager.getEngine().rpcPort
if (
request.action === EngineRecoveryAction.RestoreDefaultPort &&
previousRpcPort === ENGINE_RPC_PORT
) {
return {
ok: this.state === EngineState.Ready,
previousRpcPort,
rpcPort: previousRpcPort,
status: this.getStatus(),
}
}View on GitHub (pinned to 1a708ee577)
Solutions
- Call engineSupervisor.start() first and wait for it to attempt (success or failure) before calling recover()
- Guard the recover() call with a check that the engine has been started at least once (check getStatus().state !== initial)
- If start() never ran, trigger a fresh start instead of a recovery
Example fix
// before
await supervisor.recover({ action: EngineRecoveryAction.Retry }) // throws if start() never called
// after
await supervisor.start()
// ... if start fails ...
await supervisor.recover({ action: EngineRecoveryAction.Retry }) Defensive patterns
Strategy: validation
Validate before calling
function canRecover(supervisor: EngineSupervisor): boolean {
return supervisor.getStatus().state !== EngineState.Stopped || /* has been started */ hasBeenStarted
}
// Simpler: track whether start() was called
let hasStarted = false
await supervisor.start()
hasStarted = true
if (!hasStarted) {
throw new Error('Call start() before recover()')
} Try / catch
try {
await supervisor.recover(request)
} catch (e) {
if (e instanceof AppError && e.code === ErrorCode.EngineStartFailed && e.message.includes('before the first start')) {
await supervisor.start() // fresh start instead of recovery
} else throw e
} Prevention
- Always call start() before recover() — recovery requires prior start context
- Gate the recovery UI on engine state — only offer recovery after a start attempt has been made
- Track whether start() has been called in the controller layer
When it happens
Trigger: engineSupervisor.recover(request) is called before engineSupervisor.start() has been called at least once; binaryPath is set only inside start(), so it remains null/undefined until then.
Common situations: A UI or controller calls recover() during early initialization before the engine start sequence; a recovery flow is triggered by a stale event after the supervisor was reset; the supervisor was constructed but start() failed before setting binaryPath; a race between construction and the first start attempt.
Related errors
- EngineProcessOwnershipUnverified
- EngineProtocolError
- EngineStartFailed
- EngineProcessOwnershipUnverified
- EngineProcessTerminationFailed
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/305c127d27b88d20.
Report an issue: GitHub.