Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All documentation

Documentation

Matte

Troubleshooting

Updated

Most Matte problems show up as a status code from the API or a job that stops moving. Start with php artisan matte:doctor, run inside the Matte environment: it proves whether the binary can convert on that machine, which rules out or confirms half of what follows.

Every request returns 401

Matte refuses the credential. One of these is true:

  • The credential is missing, mistyped, revoked or expired.
  • It was minted for a purpose other than consumption. A valid credential minted for any other purpose is refused.
  • It is the wrong kind. Matte admits installation-owned credentials and signed-in users' own credentials only; an external_consumer credential always gets 401, even with the right purpose.
  • It belongs to a different Matte database. Credentials exist in one environment's database each, so a credential minted on a laptop or another environment never works here.

Run php artisan bfc:credential:list --local in the Matte environment to see what exists there. For an app attached through Scalpels, check that the app's MATTE_TOKEN is the value Scalpels wrote, and that MATTE_URL points at this environment.

A job never leaves queued

Nothing is consuming the queue. Check in Laravel Cloud that the managed queue exists, that it is attached to the Matte environment as the default queue, and that its workers are running. Do not set QUEUE_CONNECTION or MATTE_QUEUE_CONNECTION yourself to fix this; Cloud injects the first, and the second must stay unset. See Configuration.

A job is stuck at processing

A worker started the job and then stopped before it could record a result. There are two known causes:

  • The queue's job timeout fired first. Matte lets a conversion run for MATTE_TIMEOUT seconds, 120 by default. If the managed queue's own job timeout is shorter, the worker is killed from outside and the job row is never updated. Raise the managed queue's timeout above MATTE_TIMEOUT in Laravel Cloud, or set MATTE_TIMEOUT below the queue's timeout. Machine-learning conversions are the ones that run long.
  • The conversion program crashed outright instead of exiting with an error. The job row is left at processing and the queued job lands in the failed jobs list.

Either way, look at the queue's failed jobs. Your agent can list stuck jobs with the recent_jobs MCP tool filtered to processing.

A job reports failed

GET /v1/jobs/{jobId} returns the error message. The common ones:

  • "ML conversion requested, but no model file is present": the request asked for ml mode with a model that is not on the server, or the build did not download the default model. Use the default model, or switch to grabcut.
  • "Conversion failed with exit code ...": the converter could not process the image; the rest of the message is its own output. Try grabcut mode, or a different preset, to see whether the problem is the image or the engine.

POST /v1/remove?sync=1 returns 503

The binary or its libraries are not available on the machine that handled the request. Run matte:doctor there. If it reports the binary missing, the build step did not provision it: redeploy and check that the build ran matte:provision-binary. The binary has to be installed at build time; installing it after a deploy does not reach every instance.

matte:doctor reports a failure

  • Binary present and executable fails: the build step did not run, or MATTE_RUNTIME_PATH points somewhere other than where the build installed it. Leave MATTE_RUNTIME_PATH unset.
  • ONNX Runtime library present fails: the build did not complete its downloads. Redeploy.
  • Linux dynamic library resolution fails: the binary cannot load what it links against on this machine. Laravel Cloud's workers are supported; if this fails there, redeploy so the build fetches a fresh copy.
  • Real grabcut conversion fails: the binary starts but cannot convert. The details printed under the check say why.

A request returns 422 saying the client is ahead of this Matte instance

The caller speaks a newer version of Matte's wire format than the server understands. Upgrade the Matte server first, then the clients; that order is always safe.

Other 422 responses name the problem: no image, a file that is not an accepted image type, an unknown mode, preset or edge_mode, a non-integer or non-positive iterations or margin, or a callback_url field, which Matte always rejects.

A result download returns 409

The job is not done yet. The response body includes its current status. Keep polling GET /v1/jobs/{jobId} and download the result once it says done.

A result download returns 404 for a finished job

The job finished, but its PNG is no longer in the bucket. Matte never deletes results itself, so something else removed it. Submit the image again.

The connected app throws a timeout

JobHandle::wait() and AwaitRemovalJob stop waiting after MATTE_POLL_TIMEOUT seconds, 120 by default, and report a timeout even if the job later finishes. If your queue is busy or you use ml mode on large images, raise MATTE_POLL_TIMEOUT in the connected app. You can still fetch the result later with Matte::result($jobId) once the job is done.

The sign-in page returns 419

The session cannot survive between requests, so the form's CSRF token never matches. This happens with SESSION_DRIVER=array. Matte defaults to SESSION_DRIVER=cookie; do not set it to array. You do not need to sign in to operate Matte at all; see Getting started.