Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

birdietorerik's avatar

Setup Sceduled job using Laravel Forge

Hi!

Have created a sceduled job in my application thats running fine on my local PC.

Tryed to setup this daemon on laravel forge to run my job on server

Command

php artisan queue:work --sleep=3 --tries=3

But it give me error in Laravel Forge daemon

Could not open input file: artisan

What is the problem ?

0 likes
17 replies
tykus's avatar

@birdietorerik you continue to confuse Scheduled jobs with Queued jobs.

Visiting your server in Forge, there is a Scheduler link on the left to https://forge.laravel.com/servers/{your server id}/scheduler You can add a schedule for your site's artisan command if it is not there already, e.g.

php /home/forge/{project root directory}/artisan schedule:run

Aside, when you are working with Daemons for your queue worker, you need to set the directory to the root of your Laravel project so the artisan (file) command is available

1 like
birdietorerik's avatar

@tykus Hi! Yes, its already a schedule there (some other has created this) but its not working

php8.1 /home/forge/app.easyflow.golf/artisan schedule:run

In the log its saying

Could not open input file: /home/forge/app.easyflow.golf/artisan
tykus's avatar

@birdietorerik is that directory correct on your production server? And is that the root of your Laravel project?

Is your app deployed and working in the browser?

birdietorerik's avatar

@tykus Hi! When try to run this:

php8.1 /home/forge/test.easyflow.golf/current/artisan schedule:run

Now, i get this error:

   UnexpectedValueException 

  The stream or file "/home/forge/test.easyflow.golf/releases/20240911092916/storage/logs/laravel-2024-09-11.log" could not be opened in append mode: Failed to open stream: Permission denied
The exception occurred while attempting to log: Class "App\Jobs\ProcessGpsDataJob" not found
Context: {"exception":{}}

  at test.easyflow.golf/releases/20240911092916/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php:137
    133▕             }
    134▕             if (!is_resource($stream)) {
    135▕                 $this->stream = null;
    136▕ 
  ➜ 137▕                 throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record));
    138▕             }
    139▕             stream_set_chunk_size($stream, $this->streamChunkSize);
    140▕             $this->stream = $stream;
    141▕         }

      +12 vendor frames 

  13  test.easyflow.golf/releases/20240911092916/artisan:37
      Illuminate\Foundation\Console\Kernel::handle()


tykus's avatar

@birdietorerik which user are you, and which user owns the directory/log file?

It also seems that you have not resolved this problem

Class "App\Jobs\ProcessGpsDataJob" not found

Did you confirm that the PHP file is located at app/Jobs/ProcessGpsDataJob.php and the class inside is named ProcessGpsDataJob?

birdietorerik's avatar

@tykus Hi!

Yes, file App/Jobs/ProcessGpsDataJob.php exist

Here is then content of this file:

experimentor's avatar

@birdietorerik

Is the file in this path? /home/forge/test.easyflow.golf/current/app/Jobs/ProcessGpsDataJob.php

Also, generally storage/logs folder is in the same path as artisan. Whereas the error shows a different path: "/home/forge/test.easyflow.golf/releases/20240911092916/storage/logs/" @tykus is it possible to have logs in a different folder?

experimentor's avatar

Please check the files on your server. This error comes when there is no "artisan" file in the root of your project.

experimentor's avatar

You can replicate the error in your local machine by renaming "artisan" file.

Snapey's avatar

Yes, file App/Jobs/ProcessGpsDataJob.php exist

Check again. It shoukd be

app/Jobs/ProcessGpsDataJob.php

The difference is important

birdietorerik's avatar

@Snapey Hi! You right, it was wrong filename.

But still not working :(

In Forge -> Sceduler....

Command:

php8.1 /home/forge/test.easyflow.golf/artisan schedule:run

Gives me this errormessage:

Could not open input file: /home/forge/test.easyflow.golf/artisan

Have used many hours to try to fix this, no luck ???

No errors in laravel.log ?

tykus's avatar

@birdietorerik seriously, how many times did I ask you about the filename/class name??? Same about the path to the root of your project on the server.

Why is there no artisan file in the root of your project? How was it deployed to the server; did you use git or FTP or rsync or scp???

experimentor's avatar

@birdietorerik You mentioned that the path is different:

php8.1 /home/forge/test.easyflow.golf/current/artisan schedule:run

It is test.easyflow.golf/current/artisan right? not just test.easyflow.golf/artisan. There is a "current" folder in the path.

birdietorerik's avatar

@tykus

I'm not very good with servers and don't fully understand this thing about a file called artisan. Sorry that I don't get it. For example, when I want to run a command like php artisan migrate, shouldn't I just write artisan migrate?

I'm using Git, Laravel Forge, GitHub, and Laravel Envoyer. When I deploy with Laravel Envoyer, previous versions are saved as a directory with a number. The latest code runs under the directory current. Current is linked to the folder that has the numeric identifier.

experimentor's avatar
Level 25

@birdietorerik I never used forge / envoyer. I manage my own servers on AWS. So I know a bit about server paths etc...

Having said that, Forge is meant to make life easy for people who are not server experts.

Anyways, Backtracking to a previous post in this thread, you said you got a different error when you did this:

php8.1 /home/forge/test.easyflow.golf/current/artisan schedule:run

The error was:

The stream or file "/home/forge/test.easyflow.golf/releases/20240911092916/storage/logs/laravel-2024-09-11.log" could not be opened in append mode: Failed to open stream: Permission denied
The exception occurred while attempting to log: Class "App\Jobs\ProcessGpsDataJob" not found
Context: {"exception":{}}

There are 2 errors being highlighted in this:

  1. First error - not able to find the class "App\Jobs\ProcessGpsDataJob". This should ideally be in this path: "/home/forge/test.easyflow.golf/current/app/Jobs/ProcessGpsDataJob.php". If you have ssh access to your server please check using
"ls /home/forge/test.easyflow.golf/current/app/Jobs/"

If you don't ave ssh access, please check whether the file exists in you latest git repository branch which was pushed to forge.

  1. Second error: When an error occurs, laravel will try to log it to the latest file in storage/logs/. But linux servers are very strict. It is mandatory that the "user" performing the task has permission to do so. this is generally achieved by running
sudo chmod -R 775 storage

I'm assuming that forge does this automatically.

I believe a forge expert can solve this for you. But, till then, you can try this:

  1. Confirm that the file ProcessGpsDataJob.php exists in the git repo in this path: app/Jobs/ProcessGpsDataJob.php"
  2. If you have ssh access to the server confirm that it is in this path on the server: /home/forge/test.easyflow.golf/current/app/Jobs/ProcessGpsDataJob.php
  3. try sudo chmod -R 775 storage in /home/forge/test.easyflow.golf/current/@birdietorerik
JussiMannisto's avatar

@birdietorerik

For example, when I want to run a command like php artisan migrate, shouldn't I just write artisan migrate?

No. Open the artisan file. It's a PHP file, just without the extension. So when you run php artisan X Y Z, it simply runs the artisan PHP script with the command line parameters X, Y and Z.

JussiMannisto's avatar

It looks to me like you re-implemented the old command as a job. I don't see the benefit to using a job this way. You also shouldn't need to call sleep(30) as a method of rate limiting. It'll clog up your queue.

I'd pass the model(s) to the job immediately when they're created and process them in the job:

// In the controller:
$unprocessedData = UnprocessedGpsData::create(...);
$tracker = TrackerGps::where(...)->first();
ProcessGpsDataJob::dispatch($unprocessedData, $tracker);

// Job:
class ProcessGpsDataJob implements ShouldQueue
{
	...
	
	public function __construct(public UnprocessedGpsData $unprocessedData, public ?TrackerGps $tracker) 
	{ 
	}
		
	public function handle()
	{
		// Do what you need with $this->unprocessedData and $this->tracker.
	}	
}

That's all you'd need. No scheduling. No transaction locks. No looping through UnprocessedGpsData. No danger of processing the same models twice. You could pass a collection of models instead of single models if you needed.

Please or to participate in this conversation.