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

  1. Call engineSupervisor.start() first and wait for it to attempt (success or failure) before calling recover()
  2. Guard the recover() call with a check that the engine has been started at least once (check getStatus().state !== initial)
  3. 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

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


AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12). Data as JSON: /api/errors/305c127d27b88d20. Report an issue: GitHub.