Skip to main content

Troubleshooting serverless

Every heading below is a real error message or symptom, word for word, so you can find it by searching this page. All API errors arrive in the same envelope: {"error": "<message>", "request_id": "<id>"}. Include that request_id when you ask your administrator for help — it lets them find your exact request.

First stop for anything stuck: read state, message, and conditions on the service — all three sit at its top level, with no status block around them. You will find them on the console's Overview tab, or from GET .../services/<name>. state is the one-word summary and message is one sentence saying why it is not ready. Behind those sit six conditions: Ready, ServiceReady, VisibilityEnforced, RuntimeClassApplied, TrafficAccepted, and Exposed. Each one carries a reason and a plain-text message naming what is wrong.

Also compare generation with observed_generation. The first counts your edits, the second counts the edits the platform has applied. If they differ, your change was accepted and is still being rolled out. Wait before assuming it failed.

Deploying

this project cannot deploy yet: ... no Crusoe Cloud credential is mapped to this project (409)

Cause: a source deploy builds an image and stores it in a repository in your own Crusoe Cloud Registry, and this project has no Crusoe Cloud credential saved. The refusal comes at submit, before any build runs, and nothing was built.

Fix: a project admin connects the project once — Project Settings in the console, or platformctl crusoe-cloud connect --access-key-id <id> with the secret key on standard input — then deploy again. See connect your Crusoe Cloud account. Deploying an image you already published builds nothing and is unaffected.

name must be 1-52 characters of lowercase letters, digits and dashes, start with a letter and end with a letter or digit

Cause: the service name breaks the naming rule. The limit is 52 characters (not the usual 63) because the platform appends revision suffixes like -00001 to it.

Fix: rename: lowercase letters, digits, and dashes only; start with a letter; end with a letter or digit; 52 characters or fewer.

image is required - a serverless service has nothing to run without one

Cause: the create request had no image field. Serverless deploys container images — there is no source-code path on this surface.

Fix: add an image to the request body — a public registry reference, or one of your own Crusoe Container Registry repositories, including the one the platform built into if you deployed from source.

a service with that name already exists in this project (409)

Cause: names are unique per project.

Fix: pick another name, or PATCH the existing service if you meant to update it.

the service was modified concurrently - re-read it and retry (409)

Cause: someone (or something) else changed the service between your read and your PATCH. The platform refuses to silently overwrite their change.

Fix: GET the service again and re-apply your edit on top of the current state.

rejected by the platform: ... (400)

Cause: the request passed the API's own checks, but the store the platform writes to refused it. The rest of the message carries the underlying reason.

Fix: read the end of the message. It names the exact field or rule. Correct the spec and try again.

Deploy accepted, then fails minutes later with an image pull error

Cause: the machines that run your container cannot download the image. Two common causes: a reference that no host can resolve, and a private Crusoe Container Registry image in a project whose Crusoe Cloud connection is missing, stale, or was saved before that repository existed.

Fix: use a public registry reference, or a Crusoe Container Registry one, and PATCH the service with the corrected image. If the reference is already a CCR one, save the project's Crusoe Cloud connection again — that re-mints the pull credential against your current list of repositories.

FailedCreate, or quota draining faster than expected

Cause: you set a CPU or memory request without a limit. When you do that, the project stamps a default limit of 2 CPU / 4 GiB onto your container. It stamps the same limit onto the small helper container that handles your service's networking. Together those can blow past your project quota. You find out later, as FailedCreate.

Fix: set both request and limit explicitly, or leave the whole resources block out and take the defaults (250m/512Mi request, 1 CPU/512Mi limit).

Quota

SKSReady=NotReady: No Private Service Name — and nothing in the project becomes ready

Cause: the project has hit its services quota — its cap on internal routing objects — which defaults to 30. Every revision permanently holds 2 of them, and every deploy or edit creates a revision. So each deploy costs you about 2, and a service you have edited once already holds about 4. Once you reach 30 of 30, every new revision anywhere in the project gets stuck in this state. Check your live number on the console's Quotas page.

Fix: free up quota by deleting services you no longer need. Deleting a service releases all of its revisions at once. Then let the stuck revision retry. To keep it from happening again, avoid needless edits on busy projects, and read quotas and audit. This is the sharpest quota interaction on the platform. It is deliberate that a deploy creates a revision only when something actually changed.

Traffic and revisions

traffic percentages sum to N, not 100

Cause: :set-traffic replaces the entire split every time. It never merges what you send with the split that is already there. So the targets in your request must add up to exactly 100.

Fix: send the complete, final split, e.g. [{"revision_name": "api-00001", "percent": 90}, {"revision_name": "api-00002", "percent": 10}].

revision X does not exist for this service

Cause: the split names a revision that was never created (typo, or wrong number).

Fix: list real revisions with GET .../services/<name>/revisions (or the console Revisions tab) and use an exact name like checkout-api-00002.

traffic is required - send the complete split, e.g. [{"revision_name":"api-00002","percent":100}]

Cause: the :set-traffic body was empty or missing the traffic array.

Fix: the error message is the fix — send the whole split in one array.

unknown method ':<verb>' - the supported one is ':set-traffic' (404)

Cause: a custom verb other than :set-traffic was appended to the service path.

Fix: the only custom verb on this API is POST .../services/<name>:set-traffic.

min_scale N is greater than max_scale M; ...

Cause: the floor is above the ceiling. The min_scale you sent is greater than the service's max_scale. That is either the max_scale in the same request, or the one the service already had. A PATCH keeps any sub-field you leave out, so raising min_scale on its own is still checked against the existing max_scale.

Fix: raise max_scale, or lower min_scale, so the floor sits at or below the ceiling. If you are changing only one of them, GET the service first to see the other's current value, or send both in the same scaling block.

Publishing

Published, but external_url stays empty and Exposed is False

Cause: publishing finishes in the background, not while your request is open. The platform refuses to report a public URL until https://<name>-<project-short>.apps.codyhill.dev genuinely answers over valid TLS. It keeps the service's ready at false until then. The delay is usually the certificate and routing still being set up for that hostname.

Fix: wait, and keep checking. The Exposed condition's message names the step it is waiting on. If it stays False for a long time, share the condition message and a request_id with your administrator.

Console warns spec says private or converging on the visibility badge

Cause: the live service and your requested spec disagree — for example, you just flipped publish off and the change is still rolling out, or the live object drifted.

Fix: converging resolves on its own; give it a moment and refresh. A persistent mismatch is worth reporting.

RuntimeClassApplied is False

Cause: you set runtime_class_name, which asks for a stricter-than-normal container runtime by name. The platform's serving layer dropped the request, because no such runtime is enabled here. The condition's message names the value it dropped.

Fix: remove runtime_class_name from the spec. If you genuinely need stronger isolation, ask your administrator whether any runtime is available in your environment, and what to call it — nothing is enabled by default.

Logs

no running instances: this service is scaled to zero. Send it a request and the logs will appear here.

Cause: not an error — the response code is 200. The service has no running instances because it scaled to zero, so there is nothing to tail.

Fix: send the service a request, then fetch logs again. To always have logs available, set min_scale: 1 — see autoscaling.

[log stream ended: <reason>] in the middle of a log follow

Cause: the live connection to the instance ended, usually because the instance was replaced or scaled away while you were watching. Log following only ever attaches to the newest instance.

Fix: reconnect with ?follow=true. Log streaming attaches to active running instances.

Access and API mechanics

404 on a service you are sure exists

Cause: by design, "this does not exist" and "this is not your project" return the same 404. That stops outsiders from testing names to see which ones are real. So either you are pointed at the wrong project, or you have no access to it.

Fix: confirm the project ID in the path with platformctl projects list, and check your membership under projects and access. That path segment must be the project's UUID — its long unique identifier — not its name.

403 on DELETE

Cause: deleting a service requires the project admin role; members can deploy and edit but not delete. This is the only 403 on the serverless API.

Fix: ask a project admin to delete it, or to grant you the admin role.

the platform database is not configured, so project access cannot be resolved (503)

Cause: the API is up but cannot reach the platform database that stores project grants, so it cannot authorize anyone.

Fix: this is a platform-side outage, not your request. Report it to your administrator with the request_id.

page_size must be between 1 and 200

Cause: the list call asked for more than the maximum. Over-limit values are rejected, not silently clamped.

Fix: use page_size ≤ 200 and follow next_page_token for more pages.

page_token is invalid or was issued for a different list; start from the first page

Cause: a page token is a bookmark into one particular listing. Its contents mean nothing to you, and it only works for the list that produced it. You cannot reuse one across projects or across different kinds of list, and it expires with that listing.

Fix: send the list request again with no page_token, and page forward from the start.

Bursts of requests suddenly failing

Cause: the API limits each caller to about 100 requests per second, allowing a short burst of up to 200. A tight retry loop trips it.

Fix: slow the loop down. Wait a little between checks, and make that wait grow after each failure instead of hammering. A growing wait like that is called backoff. GET /healthz is exempt from the limit. Watch out for one thing there: it returns 200 even when the platform is degraded, so read the fields in its body rather than trusting the status code alone.

Still stuck?

  • Quote the request_id. Every error carries one in its envelope; it identifies your exact request in the platform's records.
  • Known issues — platform release notes and issue tracking.
  • Troubleshooting (all services) — errors that are not specific to this service.
  • Get help — who to report it to, and what to include.