Exit codes and errors
Every CapyCTL command exits 0 on success. On failure it prints one line to
standard error, error [<code>]: <message>, and exits with the status below.
With --format json the error is a JSON object with code and message.
| Exit | Code | What it means | What to do |
|---|---|---|---|
| 2 | invalid_config |
A document or command argument is invalid. | Run capyctl validate config --file <file> and fix what it reports. |
| 2 | not_found |
The deployment, instance or host named does not exist. | Check the name with capyctl list deployments or capyctl list hosts. |
| 2 | device_policy_mismatch |
A host’s file describes a card that does not match the GPU it found. | Fix the gpuN entries of resource_policy to match nvidia-smi -L. |
| 2 | missing_system_allocation |
A deployment that lists its own resources on a discrete card leaves out host RAM (system). |
Add the system entry, or leave resources out and let CapyCTL size it. |
| 2 | host_name_changed |
A standalone role’s state belongs to the host it was first started under, named after the machine’s hostname, and the hostname has changed since; a container gets a new hostname each time it is created. The message names the stored name and the current one. Nothing is changed. | Start it under the stored hostname: for a container, docker run --hostname <stored name> (Running in a container); on a machine, set the hostname back. |
| 2 | group_placement_required |
A deployment with a topology does not list its machines in placement.hosts, or also sets host, selector, strategy or max_per_host, or runs on a single-machine (standalone) role. |
List the machines in placement.hosts, head first, and remove the other placement fields (One model across machines). |
| 2 | group_topology_invalid |
placement.hosts repeats a machine, a dimension of topology is zero, or the number of machines does not divide tensor_parallel × pipeline_parallel. |
Fix topology or placement.hosts. |
| 2 | group_profile_mismatch |
The engine profile does not exist on one of the machines, or its build differs between them. | Register the same engine build under the same name on every machine. |
| 2 | group_checkpoint_mismatch |
The model’s files differ between the machines; the message lists each machine’s digest. | Make the copies identical, or let CapyCTL download the model on each machine. |
| 2 | group_model_path_mismatch |
A SGLang group with residency: deep has the model at different paths on its machines; the message names both. |
Put the model at the same path on every machine, or use residency: restart_only. |
| 2 | peer_address_missing |
A machine named in placement.hosts declares no peer address. |
Set resource_policy.groups.peer_address (or --peer-address) on that machine and restart CapyCTL there. |
| 2 | peer_address_not_local |
The declared peer address is not on any network interface of that machine. | Declare an address the machine holds on its direct link. |
| 2 | engine_env_reserved:<name> |
An engine environment variable CapyCTL sets itself (for example NCCL_*, GLOO_*, MASTER_*, VLLM_HOST_IP, CUDA_VISIBLE_DEVICES). |
Remove it; CapyCTL renders these names (the full list). |
| 2 | engine_env_not_approved:<name> |
A deployment sets an engine variable the engine profile does not approve (on some machine of a group). | Approve it with capyctl engine add ... --approve-env <name> on every machine, or remove it (Engine variables). |
| 2 | engine_env_conflict:<name> |
The same variable is in the file and on the command line. | Give it one way only. |
| 2 | custom_field_unsupported:<field> |
A deployment on a custom engine states something the engine’s manifest owns: extra_args, accept_extra_args, a tool-call or reasoning parser, memory, a draft model or a typed engine field such as dtype or max_concurrent_requests. |
Remove it; a custom engine’s deployment states only engine_config.context_length and engine_config.env (Custom engines). |
| 2 | custom_placeholder_unset:context_length |
The custom engine’s manifest uses {context_length} and the deployment states no engine_config.context_length. |
Add engine_config.context_length to the deployment. |
| 2 | custom_value_invalid:<placeholder> |
A deployment on a custom engine gives {served_name} (its first route) or {model_dir} (its model path) a value that starts with - or holds a control character, so the engine could read it as an option. |
Rename the route or move the model; a deployment cannot add options to a custom engine’s command. |
| 2 | custom_credential_ref_missing |
A custom engine profile written by hand states no security.credential_ref, or a blank one. |
Add credential_ref: "secret://engine-key" under the profile’s security (Custom engines). |
| 2 | declined |
capyctl engine add --custom asked for confirmation and the answer was not y or yes. Nothing was written. |
Run it again and answer y, after checking what it prints. |
| 3 | unauthorized |
The credentials were refused. | Check the admin token or the host’s identity. |
| 4 | insufficient_resources |
No host has the GPU memory the deployment needs. The message names each host’s limit, for example host a needs 64.0 GiB of gpu0 memory, 60.8 GiB free of its 60.8 GiB limit. |
Stop or park another deployment, start with --evict, or lower the deployment’s memory. |
| 4 | insufficient_device_memory |
The card cannot hold the model, or another program holds its memory now. | Use a smaller or quantized checkpoint, a smaller KV cache, or free the card. vLLM needs a card of about 10 GiB or more. |
| 4 | device_unobserved |
nvidia-smi gave no fresh reading for that GPU, so nothing new starts on it. |
Check that nvidia-smi works on that machine, then retry. |
| 4 | rendezvous_ports_exhausted |
Every rendezvous port of the head machine’s range is held by another group. | Stop a group on that machine, or widen --rendezvous-ports. |
| 4 | rendezvous_port_in_use:<port> |
Another program holds the rendezvous port on the head machine. CapyCTL picks another port on the next start. | Retry; if it persists, find the program holding the port. |
| 4 | service_port_in_use:<port> |
Another program holds the head’s engine port, or a SGLang worker’s local port. | Retry; if it persists, find the program holding the port. |
| 4 | host_tuning_missing:<item> |
With require_rdma: true, a machine lacks memlock (unlimited locked memory) or infiniband (read-write /dev/infiniband/uverbs*). |
Fix the item as root on that machine, or set require_rdma: false. |
| 5 | unsupported |
The request is valid but this release cannot do it. | Read the message; it names the missing capability. |
| 5 | store_from_newer_version |
The state directory was written by a newer CapyCTL. | Run the newer binary, or restore a backup taken before the upgrade. |
| 5 | multi_gpu_unsupported |
The deployment names two GPUs on one machine. | Use one GPU per machine; to spread one model over machines, give it a topology and placement.hosts. |
| 5 | unsupported_gpu_topology |
The machine has both an integrated and a discrete GPU. | Not supported in this release. |
| 5 | host_backed_unavailable |
residency: host_backed on unified memory. |
Use deep, or leave residency out. |
| 5 | capability_missing, capability_missing:deep_park |
--deep-park enabled on an engine that cannot park (TensorFold, llama.cpp, a custom engine), or a deployment asking such an engine to park (residency: deep or host_backed). |
Leave --deep-park out; use residency: restart_only. |
| 5 | group_shape_unsupported, group_shape_unsupported:<engine> |
More than one rank per machine, or a shape the engine cannot run (TensorFold runs tensor_parallel: 2 on exactly two machines; a custom engine, group_shape_unsupported:custom, runs on one machine only). |
Use one machine per rank and a shape the engine supports (Groups across machines); for a custom engine, remove topology and use instances for more copies. |
| 5 | group_instances_unsupported |
instances above 1 with a topology. |
Deploy one group per deployment. |
| 5 | group_drift:<field> |
The engine changed a multi-machine setting CapyCTL rendered (the field is named). | Report it; the engine build behaves differently from the one CapyCTL supports. |
| 5 | host_capability_missing:engine_groups |
A machine runs a CapyCTL too old for groups. | Upgrade CapyCTL on that machine. |
| 5 | capability_missing:decisions |
A deployment with api: decisions names an engine that cannot serve decision models: vLLM (any version: 0.32.0 answers Clef and Clef-Flash from their text output instead of their decision head), TensorFold, llama.cpp before 0.7.0, SGLang before 0.5.22, an installation whose registered build names no version, or a custom engine whose manifest says api: chat. |
Use llama.cpp 0.7.0 or newer or SGLang 0.5.22 or newer, registered with capyctl engine add, or a custom engine with api: decisions (Decision models). |
| 5 | capability_missing:chat |
A chat deployment (no api, or api: chat) names a custom engine whose manifest says api: decisions. |
Add api: decisions to the deployment, or use a chat engine. |
| 5 | host_capability_missing:decisions |
A machine runs a CapyCTL too old for decision models. | Upgrade CapyCTL on that machine. |
| 5 | host_capability_missing:custom_engines |
The deployment’s engine is a custom engine and a machine it names runs a CapyCTL too old for custom engines. | Upgrade CapyCTL on that machine. |
| 6 | unreconciled |
CapyCTL cannot yet tell what an engine is doing, so it will not act on it. | Wait for the host to reconnect, then retry. |
| 7 | device_conflict |
Another deployment holds the GPU exclusively. | Stop that deployment or choose another device. |
| 8 | category_limit |
A host limit on how many models of this kind may run was reached. | Stop one, or raise the limit in the host document. |
| 10 | activation_timeout |
The engine did not become ready in time. | Check the engine log; for large models raise the deployment’s timeouts.initialize, or pass --initialize-timeout to capyctl start deployment. |
| 11 | topology_unknown |
The host has not reported its GPUs yet. | Wait for the host to finish starting, then retry. |
| 12 | no_safe_estimate |
CapyCTL cannot estimate the model’s memory safely. | Declare the deployment’s resources explicitly. |
| 13 | internal |
An internal, storage or I/O failure, or a launch that failed (operation_failed). |
Read the message, capyctl status deployment <name> and the role’s log. |
| 14 | host_revoked |
The server revoked this host. The host exits and is not restarted. | Bring it back (see Several machines). |
| 15 | host_ineligible |
No allowed host can take the deployment: drained, revoked, offline or too old. | Undrain, reconnect, upgrade or re-enroll the host the message names. |
| 16 | engine_not_found |
The path holds no vllm, sglang or tensorfold package and is no llama-server binary; or, with --custom, the executable is missing or is not an executable file. |
Name the environment, its bin/vllm, bin/tensorfold or bin/python3, or a llama-server binary or its directory, or search with capyctl engine detect --path <dir>; with --custom, name the program itself. |
| 17 | engine_unsupported |
The package is not a supported engine; or llama-server --version printed no version line CapyCTL reads; or the machine has /etc/llama.cpp/config.ini, which llama-server would read as options. |
Register a vLLM, SGLang, TensorFold or llama.cpp installation; for llama.cpp, remove /etc/llama.cpp/config.ini. |
| 18 | engine_version_failed |
The engine’s version check failed or timed out; nothing was written. | Repair the installation until its version check succeeds, then add it again. |
| 19 | profile_exists |
The engine profile name is taken. | Pass --name, or remove the existing profile first. |
| 20 | profile_in_use |
Removing or replacing the engine would affect the deployments listed. | Stop them, or rerun with --drain. |
| 21 | publish_rejected |
The server refused the updated engine list; the reason follows. | Fix what the reason names. |
| 22 | agent_unreachable |
CapyCTL on this machine is running but did not answer an engine command. | Retry; if the message says the outcome is unknown, run capyctl engine list first. |
| 23 | not_interactive |
capyctl engine add without a path needs a terminal to pick one; capyctl engine add --custom needs a terminal to confirm. |
Name the installation; with --custom, pass --yes once you have checked the manifest. |
| 24 | profile_not_published |
No allowed host offers the engine profile the deployment names; nothing was stored. | Register the engine on a host with capyctl engine add <path> --name <profile>, then deploy again. |
| 25 | still_stopping |
A start came right after a stop, before the engine finished going away, or while CapyCTL was still confirming that a slow stop’s engine exited; nothing was started. |
Retry in a moment, or run capyctl start deployment <name> --wait, which waits for the stop and then starts. |
| 26 | toolchain_missing |
capyctl engine add found a TensorFold installation, but ninja, nvcc or a C++ compiler is not on the engine’s PATH (its bin, the CUDA toolkit’s bin, then /usr/local/bin, /usr/bin, /bin). TensorFold builds CUDA kernels on its first start. Nothing was written. |
Install what the message names into one of those directories, or set CUDA_HOME to a toolkit that has nvcc, then add the engine again. |
| 27 | custom_manifest_invalid |
capyctl engine add --custom could not use the manifest: it is unreadable, larger than 64 KiB, has an unknown field, uses an unknown placeholder or leaves out {host}, {port} or {model_dir}, puts a placeholder in health or idle or {api_key} outside args and env, has a health or idle.url that is not a path, or an environment that is not an absolute directory. Also a hand-written custom engine profile whose manifest_digest does not match. Nothing was written. |
Fix what the message names (The manifest), then add the engine again. |
The systemd units restart CapyCTL when it fails, except on exits a restart cannot fix: 2, 3 and 5, and 14 for a host.
A group running across machines can also show these codes in
capyctl status deployment <name> (the instance’s LAST ERROR and the
member’s row). They describe what happened to a running group; no command
exits with them:
group_member_failed: a member exited or failed; its row showsfailed. CapyCTL stops the whole group.group_member_uncertain: a member’s machine cannot be reached. The member keeps its memory charged until that machine proves it gone.group_wake_mismatch: after a wake, the model answered a fixed prompt differently than before the park. CapyCTL stops the group.group_stalled: a request got no first token withingroups.stall_timeoutand a check through the head failed too. CapyCTL stops the group.host_tuning_warning:<item>: a machine’s check found a gap (compaction,memlockorinfiniband); the group still runs.
A deployment on one machine can show these codes the same way:
wake_mismatch: after a wake, the model answered a fixed prompt differently than when it first became ready, so its weights did not come back intact. The wake fails and CapyCTL stops the instance;capyctl status deployment <name>shows the code as its last error. The next request orcapyctl start deployment <name>loads it fresh.wake_reload_failed(vLLM): during a wake, the engine reported that reloading the model’s weights failed. The wake fails and CapyCTL stops the instance, as forwake_mismatch; the next request orcapyctl start deployment <name>loads it fresh. The engine log (engine logs) has vLLM’s reason.wake_uncertain: a wake did not finish within the deployment’s wake window (timeouts.wake), or CapyCTL could not tell whether one of its steps took effect (the engine stopped answering, or did not answer the fixed prompt). The model is never served half-woken: CapyCTL stops the instance, keeping its memory charged until the engine is proven gone, and the next request loads it fresh. A wake that keeps timing out on a large model may need a longertimeouts.wakein the deployment (as in the example deployment).engine_config_file(llama.cpp):/etc/llama.cpp/config.iniexists on the machine, so llama-server would read options CapyCTL cannot check. Nothing starts until the file is removed.effective_args_mismatch(llama.cpp): once started, llama-server reported a different slot count or window, or its/metricsor/slotspage off, than CapyCTL started it with. CapyCTL stops it. Report it: the build behaves differently from llama.cpp v0.6.0.custom_listener_exposed(custom engine): once the engine passed its check, one of its processes listened on an address other than127.0.0.1or::1; the message names the address and port. CapyCTL stops the engine and the start fails. Make the manifest’sargsbind the engine to"{host}"(The listener check).
The inference endpoint answers HTTP errors with a JSON body holding code and
message (Make a request; waiting errors are listed under
Errors while waiting).
engine_error(502): the engine answered the request with an error, for example llama.cpp refusing an input longer than its batch; the message is the engine’s andretryableisfalse. On a stream it arrives as an error event. The request is not sent to another instance, and it counts against the model’s 32 running requests until the engine reports it is idle.engine_error(500) with the messagebackend completion unverified: CapyCTL could not tell what the engine did with the request (the connection failed, or the answer was cut short or unreadable). The request stays counted until CapyCTL has evidence that it ended, so many of these in a row can fill the model’s running requests (429 queue_full).
For decision models:
api_mismatch(400): the request went to the endpoint the model does not answer: a chat model asked onPOST /v1/systemone, or a decision model (api: decisions) asked onPOST /v1/chat/completions. The message names the endpoint to use; nothing was woken or sent.GET /v1/modelsshows each model’s API undercapyctl.api(Decision requests).invalid_request(400) onPOST /v1/systemone: the body is outside the decision request’s fields or shapes; the message says which.engine_rejected(400, 413 or 422): the engine refused the request as invalid, for example achoicequestion without options; the message is the engine’s. Some custom engines answer every failure this way, an engine failure included: when a request you believe valid is rejected, read the instance’s engine log (Example: basal decision models).
Troubleshooting
Section titled “Troubleshooting”A model does not start. capyctl status deployment <name> shows it in
LAST OPERATION, for example initialize failed (launch_failed), and the
instance’s LAST ERROR column gives the reason, for example
launch_failed: launch failed: engine launch failed: the engine exited before readiness.
Each launch writes
~/.local/state/capyctl/logs/<deployment id>/<launch id>.log (under the host’s
state directory on a GPU machine of several). It holds the engine’s own output
at its default level, with keys and other credentials replaced by <redacted>
and no prompts, and the management API returns its end
(GET /management/v1/deployments/<id>/engine-log,
engine logs).
A model stays queued. status deployment shows why under its table, for
example Waiting initialize pending: gave up: resource or evidence check failed: insufficient_memory: needs 109.0 GiB of system memory, 118.0 GiB available and a 11.0 GiB free reserve to keep, 2.0 GiB short:
the machine does not have that much memory available now (on a GPU,
insufficient_device_memory). Stop or park another model, free the memory from
other programs, or lower the managed limit or free reserve
(memory limits).
start --wait ends with insufficient_resources (exit 4).
The checkpoint could not be measured. status deployment shows
Checkpoint could not be measured (<reason>) and start --wait stops at once.
invalid_root: the model path is not a directory CapyCTL can read, or it sits in
a directory other users can write. unsafe_file: a link leaves the model’s
directory (or its Hugging Face cache). Fix the path or the permissions and
deploy again.
A launch refused checkpoint_mismatch. The checkpoint’s digest is not the
one the deployment declares in model.content_fingerprint, or the one CapyCTL
recorded. A sha256: value with 64 hex digits there is CapyCTL’s checkpoint
digest, taken over every file under the model directory, not the hash of a
weight file. capyctl status deployment <name> shows the declared and measured
digests; set the field to the measured one, or leave it out and CapyCTL
measures it. If the files changed on the host, restore them or deploy again.
A start refused checkpoint_unusable (exit 2). The checkpoint measured to
its digest, but the deployment’s memory does not resolve with the weights it
measured, for example a memory.request too small to leave a KV cache once
the weights are counted. The message, and Checkpoint unusable under
capyctl status deployment <name>, give the reason. Deploy a corrected
configuration (a larger memory.request, a smaller kv_cache or
context_length, or no memory.request so CapyCTL sizes it). A card too
small for the weights is refused insufficient_device_memory (exit 4)
instead.
A launch refused port_conflict. Another program listens on the engine port
CapyCTL leased. Start again (the next start takes a free port), or give CapyCTL
another range with --engine-ports.
SGLang: a permissions warning in the engine log. CapyCTL checks that the SGLang
library it uses to park and wake is not writable by other users. When the
check cannot prove that (for example, a group-writable environment on a
machine whose groups come from a directory service), CapyCTL parks anyway and
writes one warning to the engine log:
{"event":"capyctl_saver_library_permissions","problem":"group_undetermined","action":"warned"}.
To clear it, remove group and
other write permission from the engine’s environment
(chmod -R go-w <environment>).
Requests answer 401. The request has no API key or the wrong one. Read
it again from the credentials file (Make a request).
A request answers activation_failed. The model could not start; see the
first item. Each new request tries a fresh start.
After upgrading, a model is stopped. On a discrete card, the first start of 0.1.0 stops the engines an earlier release started and re-sizes their deployments; the next request starts each again (Install).

