← Control PlaneCONTENT HISTORY

Update to Control Plane

Snapshot Oct 28, 2026 · 18:05 UTC · version 1.1.0

Collection source: downloaded plugin package. These snapshots do not have a confirmed matching collection source. Differences in file lists alone do not establish changes to the package.

WHAT CHANGED

Instructions updated for workload-troubleshooting

Skill instructions — Now says: Maps a symptom to its platform-specific cause and a fix the schema accepts. Diagnosis is read-only. Most failures trace to a Control Plane rule a generic...

Observed in instructions or declared skills. Runtime behavior has not been tested.

Save Control Plane and check future changes in Following.

Up to 10 plugins free · no card required.

All fields and original values.

Skill instructions

Before

Sep 30, 2026 · 23:00 UTC

The symptom-first companion to the `workload` skill (which owns workload types, the spec shape, and the create/update tools). Given an unhealthy workload, map what you observe to its platform-specific root cause and a fix the schema will...

After

Oct 28, 2026 · 18:05 UTC

Maps a symptom to its platform-specific cause and a fix the schema accepts. Diagnosis is read-only. Most failures trace to a Control Plane rule a generic engineer would not guess; the most common is OOMKilled. ## Gather state `mcp__cpln_...

Compare saved observations

Download comparison JSON

SKILL.md text changes

− removed · + added

--- before
+++ after
@@ -5,225 +5,108 @@
 
 # Workload Troubleshooting
 
-The symptom-first companion to the `workload` skill (which owns workload types, the spec shape, and the create/update tools). Given an unhealthy workload, map what you observe to its platform-specific root cause and a fix the schema will actually accept. Diagnosis is **read-only and MCP-first**; most failures trace to a Control Plane rule a generic engineer would not guess — deny-by-default firewalls, the secret identity+policy chain, blocked ports, the sleep-binary shutdown rule. The single most common is OOMKilled. Deep remediation for each area lives in the domain skill named in that section; this skill is the diagnostic map.
+Maps a symptom to its platform-specific cause and a fix the schema accepts. Diagnosis is read-only. Most failures trace to a Control Plane rule a generic engineer would not guess; the most common is OOMKilled.
 
-## Step 1 — Gather state (read-only)
+## Gather state
 
-| Tool | What it tells you |
-|---|---|
-| `list_deployments` | **Start here.** Per-location readiness with reason/message. Pass `location` to drill into one failing location. |
-| `get_workload_events` | Image pulls, crashes, scheduling, probe failures, `OOMKilled`. |
-| `get_workload_logs` | App logs (LogQL); the `_accesslog` container holds HTTP status codes and latency. |
-| `get_resource` (kind=`workload`) | The spec and current status. |
-| `list_metrics` then `query_metrics` | Resource pressure — memory before OOM, CPU, latency. |
-| `list_workload_replicas` | Confirm which replicas are currently running. Use the `cpln` CLI after reading the `cpln` skill if in-container inspection is essential. |
-| `query_traces` then `get_trace` | For a slow or intermittently failing request: which span in the path spent the time or errored. Needs tracing enabled on the GVC (opt-in). Deep dive in `metrics-observability`. |
-
-CLI fallback (MCP unavailable, interactive shell, or CI/CD):
-
-```bash
-cpln workload get WORKLOAD --gvc GVC -o json
-cpln workload eventlog WORKLOAD --gvc GVC -o json
-cpln logs '{gvc="GVC", workload="WORKLOAD"}' --limit 50          # |= "error" filters; container="_accesslog" for HTTP codes
-cpln workload connect WORKLOAD --gvc GVC --location LOCATION      # interactive shell
-```
+`mcp__cpln__diagnose_workload` first: one call reads the deployments, events, and error log lines, matches them against this catalog, and returns the likely cause with its evidence. When it is not conclusive:
 
-## Failure catalog
-
-### Out of memory (OOMKilled) — the most common issue
+- `list_deployments`, with `location` for one location in full detail.
+- `get_workload_events` for image pulls, crashes, scheduling, probe failures, and `OOMKilled`.
+- `get_workload_logs`; the `_accesslog` container holds HTTP status codes and latency.
+- `list_metrics`, then `query_metrics`, for memory, CPU, and latency pressure.
+- `query_traces`, then `get_trace`, for a slow request; needs tracing on the GVC (`metrics-observability` skill).
+- In-container inspection goes through the CLI (`cpln` skill).
 
-**Symptoms:** container restarts repeatedly, events show `OOMKilled`, crashes under load.
-
-`memory` is a hard cap — exceed it (app + runtime + GC + buffers) and the kernel kills the container. Usual culprits: Java without `-Xmx`, Node without `--max-old-space-size`, Python loading large datasets. Each container, sidecars included, has its own limit. With **Capacity AI** on, spiky workloads can be downsized too aggressively — set `minMemory` as a floor (Capacity AI never downscales CPU below 25 millicores).
+## Failure catalog
 
-**Fix:** check real usage with `query_metrics`, then raise `memory` — but memory (MiB) must stay within **8× CPU (millicores)**, so raise `cpu` alongside it or the update is rejected (the default `cpu: 50m` caps memory at 400Mi). See *Apply fixes within the schema's limits*.
+### Out of memory (OOMKilled)
 
-### Image pull failures
+`memory` is a hard cap per container, sidecars included: app, runtime, GC, and buffers together. Usual culprits: Java without `-Xmx`, Node without `--max-old-space-size`, Python loading large datasets. Capacity AI can downsize a spiky workload too far; set `minMemory` as a floor.
 
-**Symptoms:** events show `ImagePullBackOff` / `ErrImagePull`, deployment stuck.
+**Fix:** measure with `query_metrics`, then raise `memory`, and raise `cpu` with it: memory in MiB stays within 8 times CPU in millicores, so the default `50m` caps memory at 400Mi.
 
-- **Reference format** — `//image/NAME:TAG` (org registry), bare `NAME:TAG` (Docker Hub, no `docker.io/`), full URL (other registries).
-- **Platform** — images must be `linux/amd64` for managed locations (BYOK allows more).
-- **Pull secret** — a private external registry needs a pull secret in the GVC's `pullSecretLinks`; only `docker`, `ecr`, `gcp` secret types work, and org `//image/` images need none.
+### Image pull failures (`ImagePullBackOff`, `ErrImagePull`)
 
-The registry secret must already exist (created by the user — offer a manifest scaffold for them to fill and apply, `setup-secret` skill); attach with `update_gvc`. Deep setup: `image` skill.
+Check the reference form, the architecture (`linux/amd64` on managed locations; BYOK allows more), and the pull secret: a private external registry needs a secret of type `docker`, `ecr`, or `gcp` in the GVC's `pullSecretLinks`, created with `create_secret` and `values: "user"`, attached with `update_gvc`. Org `//image/` images need none. Detail: `image` skill.
 
 ### Secret access failures
 
-**Symptoms:** env vars empty, logs show missing config, secret-access errors in events.
+Empty env vars or secret errors in events mean the identity, `reveal` policy, or reference is missing or wrong. `grant_workload_secret_access` fixes the identity and policy in one call; reference formats per type: `setup-secret` skill.
 
-A workload reaches a secret only with all three in place: an **identity linked** to it (`spec.identityLink`), a **policy granting that identity `reveal`** on the secret, and a **correct reference**. Fastest fix: `grant_workload_secret_access` builds the whole chain in one call. Reference format by type:
+### Healthy but 502 or 503: port mismatch
 
-| Type | Reference |
-|---|---|
-| Opaque (decoded / raw) | `cpln://secret/NAME.payload` / `cpln://secret/NAME` |
-| Dictionary | `cpln://secret/NAME.KEY` |
-| Username & password | `cpln://secret/NAME.username` / `.password` |
-| Keypair | `cpln://secret/NAME.secretKey` / `.publicKey` / `.passphrase` |
-| TLS | `cpln://secret/NAME.cert` / `.key` / `.chain` |
-| AWS | `cpln://secret/NAME.accessKey` / `.secretKey` / `.roleArn` / `.externalId` |
-
-The manual chain: `access-control` and `setup-secret` skills.
-
-### Port mismatch — healthy but 502/503
-
-- The spec port must match what the process listens on — compare the workload spec with application startup logs. If that is inconclusive, use the `cpln` CLI after reading the `cpln` skill for an in-container socket check.
-- On serverless, the runtime injects `PORT` and rejects a `PORT` env var that doesn't equal the exposed port.
-- **Type rules:** serverless exposes exactly one port (zero is rejected; TCP needs a dedicated LB — see *Dedicated load balancer & domain*); standard and stateful expose zero or more; cron serves no endpoint.
-- **Blocked ports** (cannot bind, invalid for TCP probes): `8012, 8022, 9090, 9091, 15000, 15001, 15006, 15020, 15021, 15090, 41000`.
+- The spec port must match what the process listens on; compare the spec with the startup logs.
+- On serverless the runtime injects `PORT` and rejects a `PORT` env var that differs from the exposed port.
+- Serverless exposes exactly one port (TCP needs a dedicated load balancer); standard and stateful expose zero or more; cron serves nothing.
+- Blocked ports cannot be bound and are invalid for TCP probes: 8012, 8022, 9090, 9091, 15000, 15001, 15006, 15020, 15021, 15090, 41000.
 
 ### Firewall blocking traffic
 
-**Symptoms:** unreachable externally, can't reach external APIs, or can't talk to other workloads.
-
-Deny-by-default: external inbound disabled, external outbound disabled, internal `none`. Fix via `update_workload`:
-
-- **Inbound** — `external.inboundAllowCIDR` (e.g. `0.0.0.0/0`, or specific CIDRs).
-- **Outbound** — `external.outboundAllowCIDR`, or `outboundAllowHostname` (hostname rules reach only ports 80, 443, 445).
-- **Internal** — `internal.inboundAllowType`: `same-gvc` / `same-org` / `workload-list` (default `none` blocks all workload-to-workload traffic).
-
-Full model: `firewall-networking` skill.
+Fix with `update_workload`: `external.inboundAllowCIDR` for inbound, `external.outboundAllowCIDR` or `outboundAllowHostname` for outbound (hostname rules reach only ports 80, 443, and 445), and `internal.inboundAllowType` or `allow_workload_access` for other workloads. Detail: `firewall-networking` skill.
 
 ### Health-check failures
 
-**Symptoms:** events show probe failures, replicas unready, restarts.
-
-Default probes: serverless gets readiness + liveness TCP on the container port; standard, stateful, and cron have none (cron strips them). Common fixes: raise `initialDelaySeconds` (0-600; default 10 readiness / 60 liveness) for slow starts; raise `periodSeconds` (1-600, default 10) or `timeoutSeconds` (1-600, default 1) for over-aggressive probes; ensure an HTTP path returns 200-399. **Readiness** failure removes the replica from the pool and pauses rollout; **liveness** failure restarts it. An autoscaled workload with no real readiness probe gets traffic before it's ready, causing 502s on scale-up — add an `httpGet` readiness probe (it needs a port; defaults to the container's). Probe tuning: `workload-security` skill.
+Serverless gets TCP readiness and liveness on the container port; standard, stateful, and cron get none. For a slow start raise `initialDelaySeconds` (0 to 600); for an over-eager probe raise `periodSeconds` (1 to 600) or `timeoutSeconds` (1 to 600, default 1); an HTTP path must return 200 to 399. A readiness failure takes the replica out of rotation and pauses the rollout; a liveness failure restarts it. An autoscaled workload without a real readiness probe takes traffic before it is ready and returns 502 on scale-up: add an `httpGet` readiness probe. Detail: `workload-security` skill.
 
-### Resource limits & Capacity AI
+### Resources and Capacity AI
 
-**Symptoms:** won't schedule, throttled, or Capacity AI not adjusting.
+- CPU and memory must fit the org quota; `maxScale` times the per-replica resources is enforced at scheduling.
+- Capacity AI is off with the `cpu` metric or multi-metric scaling; check the stored `spec.defaultOptions.capacityAI` first. On cron a new reservation lands at the next run.
+- Stateful sizing: `minCpu` to `cpu` at most 4 to 1 and 4000m apart; `minMemory` to `memory` at most 4 to 1 and 4096Mi apart.
+- Ephemeral storage is 1 GB per CPU core, at least 1 GB; exceeding it replaces the replica.
 
-- CPU/memory must fit org quota; `maxScale` × per-replica resources is enforced at scheduling.
-- **Capacity AI** does not apply with CPU-utilization or multi-metric autoscaling, or on stateful workloads — use `minCpu` / `minMemory` instead (those persist; `capacityAI` is stripped on stateful).
-- **Stateful sizing** — `minCpu`/`cpu` at most 4000m apart (ratio ≤ 4:1); `minMemory`/`memory` at most 4096Mi apart (ratio ≤ 4:1).
-- **Ephemeral storage** — 1GB per CPU core (minimum 1GB); exceeding it replaces the replica.
+### Container will not start
 
-Deep model: `autoscaling-capacity` skill.
-
-### Container won't start (restrictions)
-
-- **UID 1337** is the mesh proxy's UID — running as it excludes the container from the sidecar, disabling mesh communication and mTLS. Override `runAsUser` to another UID in 1-65534 (0/root is rejected).
-- **Reserved container names** — not `istio-proxy` / `queue-proxy` / `istio-validation` (or other reserved names); cannot start with `cpln-` or `debugger-`.
-- **Reserved env vars** — names starting `CPLN_`, plus `K_SERVICE` / `K_CONFIGURATION` / `K_REVISION`, are rejected; each value caps at 4096 characters.
-- **Suspended** — `spec.defaultOptions.suspend: true` stops the workload (min/max scale 0). Clear it, or `cpln workload start WORKLOAD --gvc GVC`.
+- UID 1337 is the mesh proxy's: a container running as it bypasses the sidecar. Set `runAsUser` to another UID from 1 to 65534; root is rejected.
+- Reserved container names: `istio-proxy`, `queue-proxy`, `istio-validation`, and any starting with `cpln-` or `debugger-`.
+- Env names starting with `CPLN_`, and `K_SERVICE`, `K_CONFIGURATION`, `K_REVISION`, are rejected; each value is at most 4096 characters.
+- `spec.defaultOptions.suspend: true` stops the workload: clear it, or `cpln workload start WORKLOAD --gvc GVC`.
 
 ### Autoscaling misconfiguration
 
-**Symptoms:** won't scale, 502s on scale-up, scale-to-zero not working, or an invalid-strategy error on create.
-
-Strategies: `concurrency`, `cpu`, `memory`, `rps`, `latency`, `keda`, `disabled`. Per type: **serverless** has no `latency` or multi-metric; **standard** has no `concurrency`; **stateful** has no `concurrency`; **cron** has no autoscaling at all (the block is removed). **Scale-to-zero:** serverless with `rps` or `concurrency`; standard and stateful only with `metric: keda` (otherwise the update is rejected); cron cannot. For 502s on scale-up, fix the readiness probe (above). Details: `autoscaling-capacity` skill.
-
-### Termination / graceful shutdown
+Serverless has no `latency` or multi-metric scaling; standard and stateful have no `concurrency`; cron has no autoscaling. Scale to zero: serverless with `rps` or `concurrency`; standard and stateful only with `metric: keda`; never cron. 502 on scale-up is the readiness probe above. Detail: `autoscaling-capacity` skill.
 
-**Symptoms:** requests fail during deploys or scale-down, 502/503 on rollout, containers killed abruptly.
+### Failures during deploys or scale-down
 
-- **Missing `sleep`** — if `sleep` is absent from **any** container, **all** containers get SIGKILL immediately (no drain). Many distroless/minimal images lack it; confirm with `which sleep`. Fix: include `sleep` or add a custom `preStop`.
-- **preStop error** — a failing custom `preStop` in any container SIGKILLs all of them.
-- **Ignores SIGTERM** — after the preStop (default `sleep 45`) the container gets the termination signal, then SIGKILL once `terminationGracePeriodSeconds` (default 90; max 900 without the `cpln/relaxGracePeriodMax` tag) elapses.
+- If `sleep` is missing from any container, every container is killed at once with no drain; distroless images often lack it (`which sleep`). Add `sleep` or a custom `preStop`.
+- A failing custom `preStop` in any container kills all of them.
+- After the preStop (default `sleep 45`) the container gets SIGTERM, then SIGKILL once `terminationGracePeriodSeconds` (default 90; at most 900 without the `cpln/relaxGracePeriodMax` tag) elapses. An app that ignores SIGTERM drops requests.
 
 Sequence and rollout options: `workload-security` skill.
 
 ### Volume mount failures
 
-**Symptoms:** can't read mounted files, permission denied, empty cloud volume.
-
-- **Secret volumes** need the identity + `reveal` policy chain (as *Secret access failures*).
-- **Cloud volumes** (S3, GCS, Azure Blob/Files) need an identity, a cloud-access policy, and outbound firewall to the provider hosts (`*.amazonaws.com`, `*.googleapis.com`, `*.blob.core.windows.net` / `*.file.core.windows.net` plus `*.azure.com`); auth is identity-only — embedded keys do not work. Read-only except Azure Files.
-- **Reserved mount paths**: `/dev`, `/dev/log`, `/tmp`, `/var`, `/var/log`. Max 15 volumes; no path may be a parent of another.
-- `filesystemGroupId` defaults to 0 (root) when unset — set it (1-65534) for a non-root app.
+- Secret volumes need the identity and `reveal` policy.
+- Cloud volumes (S3, GCS, Azure Blob and Files) need an identity with cloud access and outbound firewall to the provider hosts (`*.amazonaws.com`, `*.googleapis.com`, `*.blob.core.windows.net`, `*.file.core.windows.net`, `*.azure.com`). Only identity auth works; embedded keys do not. Read-only except Azure Files.
+- `filesystemGroupId` defaults to 0 (root) when unset: set it from 1 to 65534 for a non-root app.
 
 Volume sets: `stateful-storage` skill.
 
 ### Service-to-service failures
 
-**Symptoms:** a workload can't reach another internally (connection refused or timeout).
-
-- **Target's internal firewall** must allow the caller — `same-gvc` / `same-org` / `workload-list` (default `none`); listing a workload needs `view` on it.
-- **Endpoint** — `http://WORKLOAD.GVC.cpln.local:PORT` (use `http://`; the sidecar adds mTLS). An omitted port defaults to the target's first container port; only listed ports are reachable.
-- **Cross-GVC** — the target must allow `same-org` or list the caller; traffic may span locations and incur egress charges.
-
-### Dedicated load balancer & domain
-
-**Symptoms:** unreachable after enabling a dedicated LB, TCP broken, wrong Host header.
-
-- **TCP** needs a dedicated LB on the GVC plus a custom Domain with a TCP port — not on default endpoints (HTTP/HTTP2/gRPC only).
-- Enabling/disabling a dedicated LB causes brief DNS-propagation downtime and per-location charges.
-- **Serverless Host header** — a custom domain delivers the canonical endpoint as `Host` (the custom domain moves to `X-Forwarded-Host`); standard and stateful workloads get the custom domain as `Host`.
-- **Protocol compatibility** — the domain port protocol must match the container's: HTTP2 fronts HTTP2 or gRPC, HTTP fronts HTTP.
-
-Routing, TLS, and LBs: `domain` and `ipset-load-balancing` skills.
-
-## Apply fixes within the schema's limits
-
-A fix the Joi schema rejects at `update_workload` time is worse than none. Before applying a resource or option change, confirm it stays within these (the validator names the violated rule):
-
-- `memory` (MiB) at most 8× `cpu` (millicores); CPU ≥ 25m; memory ≥ 32MiB; `minCpu`/`minMemory` never above `cpu`/`memory`.
-- `runAsUser` / `filesystemGroupId`: 1-65534 (0/root rejected).
-- `terminationGracePeriodSeconds`: ≤ 900 (higher only with the `cpln/relaxGracePeriodMax` tag).
-- `capacityAI` with `metric: cpu` is rejected; `capacityAI` is stripped on stateful, cron, and vm.
-- Standard/stateful `minScale: 0` requires `metric: keda`; cron and vm cannot scale to zero.
-- A metric outside the workload type's allow-list is rejected.
-
-Prefer `update_workload` (PATCH — only the fields you set change). For full manifest control, author against `get_resource_schema` then `cpln apply -f workload.yaml --gvc GVC`.
-
-## Verify
-
-After applying, poll `list_deployments` until every location reports ready, confirm the original symptom cleared (events/logs), and report the **canonical endpoint** `list_deployments` returns — never a constructed URL. For a public workload, confirm it actually responds, not just that it is ready.
-
-## Troubleshooting
-
-| Symptom | Likely cause | First check |
-|---|---|---|
-| Restarts; `OOMKilled` in events | memory cap too low (or Capacity AI downsized) | `query_metrics` memory; raise `memory` + `cpu` |
-| `ImagePullBackOff` / stuck | bad image ref, wrong platform, missing pull secret | events; GVC `pullSecretLinks` |
-| Env vars empty | broken identity + `reveal` chain or wrong reference | `grant_workload_secret_access` |
-| Healthy but 502/503 | spec port ≠ listening port, or a blocked port | workload spec and startup logs |
-| Unreachable / can't call out | deny-by-default firewall | `firewallConfig` |
-| Won't become ready | probe path/port wrong or too aggressive | events; probe config |
-| Can't reach another workload | target internal firewall `none`, or `https://` used | target `inboundAllowType`; use `http://` |
-| 502/503 during deploys | missing `sleep`, or app ignores SIGTERM | `which sleep`; SIGTERM handling |
-| `update_workload` rejected | the fix violates a schema limit | *Apply fixes within the schema's limits* |
-
-## Quick reference
-
-### MCP tools
-
-| Tool | Purpose |
-|---|---|
-| `list_deployments` | Primary per-location readiness monitor |
-| `get_workload_events` | Image / crash / probe / schedule events |
-| `get_workload_logs` | App and `_accesslog` logs |
-| `list_workload_replicas` | List running replicas |
-| `query_metrics` (after `list_metrics`) | Memory / CPU / latency pressure |
-| `update_workload` | Apply a spec fix (PATCH) |
-| `grant_workload_secret_access` | Build the identity + `reveal` chain in one call |
-| `get_resource_schema` | Exact fields before a manifest-level fix |
+- The target's internal firewall must admit the caller (`allow_workload_access`); listing a workload needs `view` on it.
+- Call `http://WORKLOAD.GVC.cpln.local:PORT` over plain HTTP. An omitted port means the target's first container port; only listed ports are reachable.
+- Cross-GVC calls may span locations and pay egress.
 
-### CLI (fallback)
+### Dedicated load balancer and domains
 
-Use when MCP is unavailable, for an interactive shell, or in CI/CD (service-account `CPLN_TOKEN`).
+- TCP needs a dedicated load balancer on the GVC plus a Domain with a TCP port; default endpoints serve HTTP, HTTP2, and gRPC only.
+- Enabling or disabling a dedicated load balancer causes a brief DNS propagation outage and per-location charges.
+- Behind a custom domain, a serverless workload receives the canonical endpoint as `Host` (the custom domain arrives in `X-Forwarded-Host`); standard and stateful receive the custom domain.
+- The domain port protocol must match the container's: HTTP2 fronts HTTP2 or gRPC, HTTP fronts HTTP.
 
-```bash
-cpln workload get WORKLOAD --gvc GVC -o json
-cpln workload eventlog WORKLOAD --gvc GVC -o json
-cpln workload connect WORKLOAD --gvc GVC --location LOCATION
-cpln apply -f workload.yaml --gvc GVC
-```
+Detail: `domain` and `ipset-load-balancing` skills.
 
-### Related skills
+## Fix within the schema's limits
 
-- `workload` — primary skill (types, spec shape, tool division); start here.
-- `workload-security` — probe tuning, termination, `securityOptions`, direct LBs.
-- `firewall-networking` — inbound / outbound / internal rules.
-- `autoscaling-capacity` — scaling strategies and Capacity AI.
-- `access-control` / `setup-secret` — the identity + `reveal` chain.
-- `stateful-storage` — volume sets; `domain` / `ipset-load-balancing` — routing and LBs; `image` — registries and pull secrets.
+A fix the validator rejects wastes a round trip. Before a resource or option change, check:
 
-## Documentation
+- Memory in MiB at most 8 times CPU in millicores; CPU at least 25m; memory at least 32Mi; `minCpu` and `minMemory` never above `cpu` and `memory`.
+- `runAsUser` and `filesystemGroupId` from 1 to 65534.
+- `terminationGracePeriodSeconds` at most 900, unless the `cpln/relaxGracePeriodMax` tag is set.
+- `capacityAI` is rejected with `metric: cpu` and with a GPU container.
+- Standard and stateful `minScale: 0` needs `metric: keda`; cron and vm cannot scale to zero.
+- A metric outside the type's list is rejected.
 
-- [Workload Types](https://docs.controlplane.com/reference/workload/types.md)
-- [Containers](https://docs.controlplane.com/reference/workload/containers.md)
-- [Capacity AI](https://docs.controlplane.com/reference/workload/capacity.md)
-- [Firewall](https://docs.controlplane.com/reference/workload/firewall.md)
-- [Termination](https://docs.controlplane.com/reference/workload/termination.md)
+A restart without a spec change (fresh secret values, wedged replicas) is `restart_workload`. After a fix, `diagnose_workload` confirms the symptom cleared.
Full technical diff · 1 changed fields

changed /skill_md_contents

BEFORE
"---\nname: workload-troubleshooting\ndescription: \"Diagnoses unhealthy Control Plane workloads. Use when asked why a workload is crashing, not starting, OOMKilled, ImagePullBackOff, returning 502s, failing health checks, unreachable, or stuck deploying.\"\n---\n\n# Workload Troubleshooting\n\nThe symptom-first companion to the `workload` skill (which owns workload types, the spec shape, and the create/update tools). Given an unhealthy workload, map what you observe to its platform-specific root cause and a fix the schema will actually accept. Diagnosis is **read-only and MCP-first**; most failures trace to a Control Plane rule a generic engineer would not guess — deny-by-default firewalls, the secret identity+policy chain, blocked ports, the sleep-binary shutdown rule. The single most common is OOMKilled. Deep remediation for each area lives in the domain skill named in that section; this skill is the diagnostic map.\n\n## Step 1 — Gather state (read-only)\n\n| Tool | What it tells you |\n|---|---|\n| `list_deployments` | **Start here.** Per-location readiness with reason/message. Pass `location` to drill into one failing location. |\n| `get_workload_events` | Image pulls, crashes, scheduling, probe failures, `OOMKilled`. |\n| `get_workload_logs` | App logs (LogQL); the `_accesslog` container holds HTTP status codes and latency. |\n| `get_resource` (kind=`workload`) | The spec and current status. |\n| `list_metrics` then `query_metrics` | Resource pressure — memory before OOM, CPU, latency. |\n| `list_workload_replicas` | Confirm which replicas are currently running. Use the `cpln` CLI after reading the `cpln` skill if in-container inspection is essential. |\n| `query_traces` then `get_trace` | For a slow or intermittently failing request: which span in the path spent the time or errored. Needs tracing enabled on the GVC (opt-in). Deep dive in `metrics-observability`. |\n\nCLI fallback (MCP unavailable, interactive shell, or CI/CD):\n\n```bash\ncpln workload get WORKLOAD --gvc GVC -o json\ncpln workload eventlog WORKLOAD --gvc GVC -o json\ncpln logs '{gvc=\"GVC\", workload=\"WORKLOAD\"}' --limit 50          # |= \"error\" filters; container=\"_accesslog\" for HTTP codes\ncpln workload connect WORKLOAD --gvc GVC --location LOCATION      # interactive shell\n```\n\n## Failure catalog\n\n### Out of memory (OOMKilled) — the most common issue\n\n**Symptoms:** container restarts repeatedly, events show `OOMKilled`, crashes under load.\n\n`memory` is a hard cap — exceed it (app + runtime + GC + buffers) and the kernel kills the container. Usual culprits: Java without `-Xmx`, Node without `--max-old-space-size`, Python loading large datasets. Each container, sidecars included, has its own limit. With **Capacity AI** on, spiky workloads can be downsized too aggressively — set `minMemory` as a floor (Capacity AI never downscales CPU below 25 millicores).\n\n**Fix:** check real usage with `query_metrics`, then raise `memory` — but memory (MiB) must stay within **8× CPU (millicores)**, so raise `cpu` alongside it or the update is rejected (the default `cpu: 50m` caps memory at 400Mi). See *Apply fixes within the schema's limits*.\n\n### Image pull failures\n\n**Symptoms:** events show `ImagePullBackOff` / `ErrImagePull`, deployment stuck.\n\n- **Reference format** — `//image/NAME:TAG` (org registry), bare `NAME:TAG` (Docker Hub, no `docker.io/`), full URL (other registries).\n- **Platform** — images must be `linux/amd64` for managed locations (BYOK allows more).\n- **Pull secret** — a private external registry needs a pull secret in the GVC's `pullSecretLinks`; only `docker`, `ecr`, `gcp` secret types work, and org `//image/` images need none.\n\nThe registry secret must already exist (created by the user — offer a manifest scaffold for them to fill and apply, `setup-secret` skill); attach with `update_gvc`. Deep setup: `image` skill.\n\n### Secret access failures\n\n**Symptoms:** env vars empty, logs show missing config, secret-access errors in events.\n\nA workload reaches a secret only with all three in place: an **identity linked** to it (`spec.identityLink`), a **policy granting that identity `reveal`** on the secret, and a **correct reference**. Fastest fix: `grant_workload_secret_access` builds the whole chain in one call. Reference format by type:\n\n| Type | Reference |\n|---|---|\n| Opaque (decoded / raw) | `cpln://secret/NAME.payload` / `cpln://secret/NAME` |\n| Dictionary | `cpln://secret/NAME.KEY` |\n| Username & password | `cpln://secret/NAME.username` / `.password` |\n| Keypair | `cpln://secret/NAME.secretKey` / `.publicKey` / `.passphrase` |\n| TLS | `cpln://secret/NAME.cert` / `.key` / `.chain` |\n| AWS | `cpln://secret/NAME.accessKey` / `.secretKey` / `.roleArn` / `.externalId` |\n\nThe manual chain: `access-control` and `setup-secret` skills.\n\n### Port mismatch — healthy but 502/503\n\n- The spec port must match what the process listens on — compare the workload spec with application startup logs. If that is inconclusive, use the `cpln` CLI after reading the `cpln` skill for an in-container socket check.\n- On serverless, the runtime injects `PORT` and rejects a `PORT` env var that doesn't equal the exposed port.\n- **Type rules:** serverless exposes exactly one port (zero is rejected; TCP needs a dedicated LB — see *Dedicated load balancer & domain*); standard and stateful expose zero or more; cron serves no endpoint.\n- **Blocked ports** (cannot bind, invalid for TCP probes): `8012, 8022, 9090, 9091, 15000, 15001, 15006, 15020, 15021, 15090, 41000`.\n\n### Firewall blocking traffic\n\n**Symptoms:** unreachable externally, can't reach external APIs, or can't talk to other workloads.\n\nDeny-by-default: external inbound disabled, external outbound disabled, internal `none`. Fix via `update_workload`:\n\n- **Inbound** — `external.inboundAllowCIDR` (e.g. `0.0.0.0/0`, or specific CIDRs).\n- **Outbound** — `external.outboundAllowCIDR`, or `outboundAllowHostname` (hostname rules reach only ports 80, 443, 445).\n- **Internal** — `internal.inboundAllowType`: `same-gvc` / `same-org` / `workload-list` (default `none` blocks all workload-to-workload traffic).\n\nFull model: `firewall-networking` skill.\n\n### Health-check failures\n\n**Symptoms:** events show probe failures, replicas unready, restarts.\n\nDefault probes: serverless gets readiness + liveness TCP on the container port; standard, stateful, and cron have none (cron strips them). Common fixes: raise `initialDelaySeconds` (0-600; default 10 readiness / 60 liveness) for slow starts; raise `periodSeconds` (1-600, default 10) or `timeoutSeconds` (1-600, default 1) for over-aggressive probes; ensure an HTTP path returns 200-399. **Readiness** failure removes the replica from the pool and pauses rollout; **liveness** failure restarts it. An autoscaled workload with no real readiness probe gets traffic before it's ready, causing 502s on scale-up — add an `httpGet` readiness probe (it needs a port; defaults to the container's). Probe tuning: `workload-security` skill.\n\n### Resource limits & Capacity AI\n\n**Symptoms:** won't schedule, throttled, or Capacity AI not adjusting.\n\n- CPU/memory must fit org quota; `maxScale` × per-replica resources is enforced at scheduling.\n- **Capacity AI** does not apply with CPU-utilization or multi-metric autoscaling, or on stateful workloads — use `minCpu` / `minMemory` instead (those persist; `capacityAI` is stripped on stateful).\n- **Stateful sizing** — `minCpu`/`cpu` at most 4000m apart (ratio ≤ 4:1); `minMemory`/`memory` at most 4096Mi apart (ratio ≤ 4:1).\n- **Ephemeral storage** — 1GB per CPU core (minimum 1GB); exceeding it replaces the replica.\n\nDeep model: `autoscaling-capacity` skill.\n\n### Container won't start (restrictions)\n\n- **UID 1337** is the mesh proxy's UID — running as it excludes the container from the sidecar, disabling mesh communication and mTLS. Override `runAsUser` to another UID in 1-65534 (0/root is rejected).\n- **Reserved container names** — not `istio-proxy` / `queue-proxy` / `istio-validation` (or other reserved names); cannot start with `cpln-` or `debugger-`.\n- **Reserved env vars** — names starting `CPLN_`, plus `K_SERVICE` / `K_CONFIGURATION` / `K_REVISION`, are rejected; each value caps at 4096 characters.\n- **Suspended** — `spec.defaultOptions.suspend: true` stops the workload (min/max scale 0). Clear it, or `cpln workload start WORKLOAD --gvc GVC`.\n\n### Autoscaling misconfiguration\n\n**Symptoms:** won't scale, 502s on scale-up, scale-to-zero not working, or an invalid-strategy error on create.\n\nStrategies: `concurrency`, `cpu`, `memory`, `rps`, `latency`, `keda`, `disabled`. Per type: **serverless** has no `latency` or multi-metric; **standard** has no `concurrency`; **stateful** has no `concurrency`; **cron** has no autoscaling at all (the block is removed). **Scale-to-zero:** serverless with `rps` or `concurrency`; standard and stateful only with `metric: keda` (otherwise the update is rejected); cron cannot. For 502s on scale-up, fix the readiness probe (above). Details: `autoscaling-capacity` skill.\n\n### Termination / graceful shutdown\n\n**Symptoms:** requests fail during deploys or scale-down, 502/503 on rollout, containers killed abruptly.\n\n- **Missing `sleep`** — if `sleep` is absent from **any** container, **all** containers get SIGKILL immediately (no drain). Many distroless/minimal images lack it; confirm with `which sleep`. Fix: include `sleep` or add a custom `preStop`.\n- **preStop error** — a failing custom `preStop` in any container SIGKILLs all of them.\n- **Ignores SIGTERM** — after the preStop (default `sleep 45`) the container gets the termination signal, then SIGKILL once `terminationGracePeriodSeconds` (default 90; max 900 without the `cpln/relaxGracePeriodMax` tag) elapses.\n\nSequence and rollout options: `workload-security` skill.\n\n### Volume mount failures\n\n**Symptoms:** can't read mounted files, permission denied, empty cloud volume.\n\n- **Secret volumes** need the identity + `reveal` policy chain (as *Secret access failures*).\n- **Cloud volumes** (S3, GCS, Azure Blob/Files) need an identity, a cloud-access policy, and outbound firewall to the provider hosts (`*.amazonaws.com`, `*.googleapis.com`, `*.blob.core.windows.net` / `*.file.core.windows.net` plus `*.azure.com`); auth is identity-only — embedded keys do not work. Read-only except Azure Files.\n- **Reserved mount paths**: `/dev`, `/dev/log`, `/tmp`, `/var`, `/var/log`. Max 15 volumes; no path may be a parent of another.\n- `filesystemGroupId` defaults to 0 (root) when unset — set it (1-65534) for a non-root app.\n\nVolume sets: `stateful-storage` skill.\n\n### Service-to-service failures\n\n**Symptoms:** a workload can't reach another internally (connection refused or timeout).\n\n- **Target's internal firewall** must allow the caller — `same-gvc` / `same-org` / `workload-list` (default `none`); listing a workload needs `view` on it.\n- **Endpoint** — `http://WORKLOAD.GVC.cpln.local:PORT` (use `http://`; the sidecar adds mTLS). An omitted port defaults to the target's first container port; only listed ports are reachable.\n- **Cross-GVC** — the target must allow `same-org` or list the caller; traffic may span locations and incur egress charges.\n\n### Dedicated load balancer & domain\n\n**Symptoms:** unreachable after enabling a dedicated LB, TCP broken, wrong Host header.\n\n- **TCP** needs a dedicated LB on the GVC plus a custom Domain with a TCP port — not on default endpoints (HTTP/HTTP2/gRPC only).\n- Enabling/disabling a dedicated LB causes brief DNS-propagation downtime and per-location charges.\n- **Serverless Host header** — a custom domain delivers the canonical endpoint as `Host` (the custom domain moves to `X-Forwarded-Host`); standard and stateful workloads get the custom domain as `Host`.\n- **Protocol compatibility** — the domain port protocol must match the container's: HTTP2 fronts HTTP2 or gRPC, HTTP fronts HTTP.\n\nRouting, TLS, and LBs: `domain` and `ipset-load-balancing` skills.\n\n## Apply fixes within the schema's limits\n\nA fix the Joi schema rejects at `update_workload` time is worse than none. Before applying a resource or option change, confirm it stays within these (the validator names the violated rule):\n\n- `memory` (MiB) at most 8× `cpu` (millicores); CPU ≥ 25m; memory ≥ 32MiB; `minCpu`/`minMemory` never above `cpu`/`memory`.\n- `runAsUser` / `filesystemGroupId`: 1-65534 (0/root rejected).\n- `terminationGracePeriodSeconds`: ≤ 900 (higher only with the `cpln/relaxGracePeriodMax` tag).\n- `capacityAI` with `metric: cpu` is rejected; `capacityAI` is stripped on stateful, cron, and vm.\n- Standard/stateful `minScale: 0` requires `metric: keda`; cron and vm cannot scale to zero.\n- A metric outside the workload type's allow-list is rejected.\n\nPrefer `update_workload` (PATCH — only the fields you set change). For full manifest control, author against `get_resource_schema` then `cpln apply -f workload.yaml --gvc GVC`.\n\n## Verify\n\nAfter applying, poll `list_deployments` until every location reports ready, confirm the original symptom cleared (events/logs), and report the **canonical endpoint** `list_deployments` returns — never a constructed URL. For a public workload, confirm it actually responds, not just that it is ready.\n\n## Troubleshooting\n\n| Symptom | Likely cause | First check |\n|---|---|---|\n| Restarts; `OOMKilled` in events | memory cap too low (or Capacity AI downsized) | `query_metrics` memory; raise `memory` + `cpu` |\n| `ImagePullBackOff` / stuck | bad image ref, wrong platform, missing pull secret | events; GVC `pullSecretLinks` |\n| Env vars empty | broken identity + `reveal` chain or wrong reference | `grant_workload_secret_access` |\n| Healthy but 502/503 | spec port ≠ listening port, or a blocked port | workload spec and startup logs |\n| Unreachable / can't call out | deny-by-default firewall | `firewallConfig` |\n| Won't become ready | probe path/port wrong or too aggressive | events; probe config |\n| Can't reach another workload | target internal firewall `none`, or `https://` used | target `inboundAllowType`; use `http://` |\n| 502/503 during deploys | missing `sleep`, or app ignores SIGTERM | `which sleep`; SIGTERM handling |\n| `update_workload` rejected | the fix violates a schema limit | *Apply fixes within the schema's limits* |\n\n## Quick reference\n\n### MCP tools\n\n| Tool | Purpose |\n|---|---|\n| `list_deployments` | Primary per-location readiness monitor |\n| `get_workload_events` | Image / crash / probe / schedule events |\n| `get_workload_logs` | App and `_accesslog` logs |\n| `list_workload_replicas` | List running replicas |\n| `query_metrics` (after `list_metrics`) | Memory / CPU / latency pressure |\n| `update_workload` | Apply a spec fix (PATCH) |\n| `grant_workload_secret_access` | Build the identity + `reveal` chain in one call |\n| `get_resource_schema` | Exact fields before a manifest-level fix |\n\n### CLI (fallback)\n\nUse when MCP is unavailable, for an interactive shell, or in CI/CD (service-account `CPLN_TOKEN`).\n\n```bash\ncpln workload get WORKLOAD --gvc GVC -o json\ncpln workload eventlog WORKLOAD --gvc GVC -o json\ncpln workload connect WORKLOAD --gvc GVC --location LOCATION\ncpln apply -f workload.yaml --gvc GVC\n```\n\n### Related skills\n\n- `workload` — primary skill (types, spec shape, tool division); start here.\n- `workload-security` — probe tuning, termination, `securityOptions`, direct LBs.\n- `firewall-networking` — inbound / outbound / internal rules.\n- `autoscaling-capacity` — scaling strategies and Capacity AI.\n- `access-control` / `setup-secret` — the identity + `reveal` chain.\n- `stateful-storage` — volume sets; `domain` / `ipset-load-balancing` — routing and LBs; `image` — registries and pull secrets.\n\n## Documentation\n\n- [Workload Types](https://docs.controlplane.com/reference/workload/types.md)\n- [Containers](https://docs.controlplane.com/reference/workload/containers.md)\n- [Capacity AI](https://docs.controlplane.com/reference/workload/capacity.md)\n- [Firewall](https://docs.controlplane.com/reference/workload/firewall.md)\n- [Termination](https://docs.controlplane.com/reference/workload/termination.md)\n"
AFTER
"---\nname: workload-troubleshooting\ndescription: \"Diagnoses unhealthy Control Plane workloads. Use when asked why a workload is crashing, not starting, OOMKilled, ImagePullBackOff, returning 502s, failing health checks, unreachable, or stuck deploying.\"\n---\n\n# Workload Troubleshooting\n\nMaps a symptom to its platform-specific cause and a fix the schema accepts. Diagnosis is read-only. Most failures trace to a Control Plane rule a generic engineer would not guess; the most common is OOMKilled.\n\n## Gather state\n\n`mcp__cpln__diagnose_workload` first: one call reads the deployments, events, and error log lines, matches them against this catalog, and returns the likely cause with its evidence. When it is not conclusive:\n\n- `list_deployments`, with `location` for one location in full detail.\n- `get_workload_events` for image pulls, crashes, scheduling, probe failures, and `OOMKilled`.\n- `get_workload_logs`; the `_accesslog` container holds HTTP status codes and latency.\n- `list_metrics`, then `query_metrics`, for memory, CPU, and latency pressure.\n- `query_traces`, then `get_trace`, for a slow request; needs tracing on the GVC (`metrics-observability` skill).\n- In-container inspection goes through the CLI (`cpln` skill).\n\n## Failure catalog\n\n### Out of memory (OOMKilled)\n\n`memory` is a hard cap per container, sidecars included: app, runtime, GC, and buffers together. Usual culprits: Java without `-Xmx`, Node without `--max-old-space-size`, Python loading large datasets. Capacity AI can downsize a spiky workload too far; set `minMemory` as a floor.\n\n**Fix:** measure with `query_metrics`, then raise `memory`, and raise `cpu` with it: memory in MiB stays within 8 times CPU in millicores, so the default `50m` caps memory at 400Mi.\n\n### Image pull failures (`ImagePullBackOff`, `ErrImagePull`)\n\nCheck the reference form, the architecture (`linux/amd64` on managed locations; BYOK allows more), and the pull secret: a private external registry needs a secret of type `docker`, `ecr`, or `gcp` in the GVC's `pullSecretLinks`, created with `create_secret` and `values: \"user\"`, attached with `update_gvc`. Org `//image/` images need none. Detail: `image` skill.\n\n### Secret access failures\n\nEmpty env vars or secret errors in events mean the identity, `reveal` policy, or reference is missing or wrong. `grant_workload_secret_access` fixes the identity and policy in one call; reference formats per type: `setup-secret` skill.\n\n### Healthy but 502 or 503: port mismatch\n\n- The spec port must match what the process listens on; compare the spec with the startup logs.\n- On serverless the runtime injects `PORT` and rejects a `PORT` env var that differs from the exposed port.\n- Serverless exposes exactly one port (TCP needs a dedicated load balancer); standard and stateful expose zero or more; cron serves nothing.\n- Blocked ports cannot be bound and are invalid for TCP probes: 8012, 8022, 9090, 9091, 15000, 15001, 15006, 15020, 15021, 15090, 41000.\n\n### Firewall blocking traffic\n\nFix with `update_workload`: `external.inboundAllowCIDR` for inbound, `external.outboundAllowCIDR` or `outboundAllowHostname` for outbound (hostname rules reach only ports 80, 443, and 445), and `internal.inboundAllowType` or `allow_workload_access` for other workloads. Detail: `firewall-networking` skill.\n\n### Health-check failures\n\nServerless gets TCP readiness and liveness on the container port; standard, stateful, and cron get none. For a slow start raise `initialDelaySeconds` (0 to 600); for an over-eager probe raise `periodSeconds` (1 to 600) or `timeoutSeconds` (1 to 600, default 1); an HTTP path must return 200 to 399. A readiness failure takes the replica out of rotation and pauses the rollout; a liveness failure restarts it. An autoscaled workload without a real readiness probe takes traffic before it is ready and returns 502 on scale-up: add an `httpGet` readiness probe. Detail: `workload-security` skill.\n\n### Resources and Capacity AI\n\n- CPU and memory must fit the org quota; `maxScale` times the per-replica resources is enforced at scheduling.\n- Capacity AI is off with the `cpu` metric or multi-metric scaling; check the stored `spec.defaultOptions.capacityAI` first. On cron a new reservation lands at the next run.\n- Stateful sizing: `minCpu` to `cpu` at most 4 to 1 and 4000m apart; `minMemory` to `memory` at most 4 to 1 and 4096Mi apart.\n- Ephemeral storage is 1 GB per CPU core, at least 1 GB; exceeding it replaces the replica.\n\n### Container will not start\n\n- UID 1337 is the mesh proxy's: a container running as it bypasses the sidecar. Set `runAsUser` to another UID from 1 to 65534; root is rejected.\n- Reserved container names: `istio-proxy`, `queue-proxy`, `istio-validation`, and any starting with `cpln-` or `debugger-`.\n- Env names starting with `CPLN_`, and `K_SERVICE`, `K_CONFIGURATION`, `K_REVISION`, are rejected; each value is at most 4096 characters.\n- `spec.defaultOptions.suspend: true` stops the workload: clear it, or `cpln workload start WORKLOAD --gvc GVC`.\n\n### Autoscaling misconfiguration\n\nServerless has no `latency` or multi-metric scaling; standard and stateful have no `concurrency`; cron has no autoscaling. Scale to zero: serverless with `rps` or `concurrency`; standard and stateful only with `metric: keda`; never cron. 502 on scale-up is the readiness probe above. Detail: `autoscaling-capacity` skill.\n\n### Failures during deploys or scale-down\n\n- If `sleep` is missing from any container, every container is killed at once with no drain; distroless images often lack it (`which sleep`). Add `sleep` or a custom `preStop`.\n- A failing custom `preStop` in any container kills all of them.\n- After the preStop (default `sleep 45`) the container gets SIGTERM, then SIGKILL once `terminationGracePeriodSeconds` (default 90; at most 900 without the `cpln/relaxGracePeriodMax` tag) elapses. An app that ignores SIGTERM drops requests.\n\nSequence and rollout options: `workload-security` skill.\n\n### Volume mount failures\n\n- Secret volumes need the identity and `reveal` policy.\n- Cloud volumes (S3, GCS, Azure Blob and Files) need an identity with cloud access and outbound firewall to the provider hosts (`*.amazonaws.com`, `*.googleapis.com`, `*.blob.core.windows.net`, `*.file.core.windows.net`, `*.azure.com`). Only identity auth works; embedded keys do not. Read-only except Azure Files.\n- `filesystemGroupId` defaults to 0 (root) when unset: set it from 1 to 65534 for a non-root app.\n\nVolume sets: `stateful-storage` skill.\n\n### Service-to-service failures\n\n- The target's internal firewall must admit the caller (`allow_workload_access`); listing a workload needs `view` on it.\n- Call `http://WORKLOAD.GVC.cpln.local:PORT` over plain HTTP. An omitted port means the target's first container port; only listed ports are reachable.\n- Cross-GVC calls may span locations and pay egress.\n\n### Dedicated load balancer and domains\n\n- TCP needs a dedicated load balancer on the GVC plus a Domain with a TCP port; default endpoints serve HTTP, HTTP2, and gRPC only.\n- Enabling or disabling a dedicated load balancer causes a brief DNS propagation outage and per-location charges.\n- Behind a custom domain, a serverless workload receives the canonical endpoint as `Host` (the custom domain arrives in `X-Forwarded-Host`); standard and stateful receive the custom domain.\n- The domain port protocol must match the container's: HTTP2 fronts HTTP2 or gRPC, HTTP fronts HTTP.\n\nDetail: `domain` and `ipset-load-balancing` skills.\n\n## Fix within the schema's limits\n\nA fix the validator rejects wastes a round trip. Before a resource or option change, check:\n\n- Memory in MiB at most 8 times CPU in millicores; CPU at least 25m; memory at least 32Mi; `minCpu` and `minMemory` never above `cpu` and `memory`.\n- `runAsUser` and `filesystemGroupId` from 1 to 65534.\n- `terminationGracePeriodSeconds` at most 900, unless the `cpln/relaxGracePeriodMax` tag is set.\n- `capacityAI` is rejected with `metric: cpu` and with a GPU container.\n- Standard and stateful `minScale: 0` needs `metric: keda`; cron and vm cannot scale to zero.\n- A metric outside the type's list is rejected.\n\nA restart without a spec change (fresh secret values, wedged replicas) is `restart_workload`. After a fix, `diagnose_workload` confirms the symptom cleared.\n"
Full snapshot data
{
  "description": "Diagnoses unhealthy Control Plane workloads. Use when asked why a workload is crashing, not starting, OOMKilled, ImagePullBackOff, returning 502s, failing health checks, unreachable, or stuck deploying.",
  "included_files": [],
  "name": "workload-troubleshooting",
  "skill_md_contents": "---\nname: workload-troubleshooting\ndescription: \"Diagnoses unhealthy Control Plane workloads. Use when asked why a workload is crashing, not starting, OOMKilled, ImagePullBackOff, returning 502s, failing health checks, unreachable, or stuck deploying.\"\n---\n\n# Workload Troubleshooting\n\nMaps a symptom to its platform-specific cause and a fix the schema accepts. Diagnosis is read-only. Most failures trace to a Control Plane rule a generic engineer would not guess; the most common is OOMKilled.\n\n## Gather state\n\n`mcp__cpln__diagnose_workload` first: one call reads the deployments, events, and error log lines, matches them against this catalog, and returns the likely cause with its evidence. When it is not conclusive:\n\n- `list_deployments`, with `location` for one location in full detail.\n- `get_workload_events` for image pulls, crashes, scheduling, probe failures, and `OOMKilled`.\n- `get_workload_logs`; the `_accesslog` container holds HTTP status codes and latency.\n- `list_metrics`, then `query_metrics`, for memory, CPU, and latency pressure.\n- `query_traces`, then `get_trace`, for a slow request; needs tracing on the GVC (`metrics-observability` skill).\n- In-container inspection goes through the CLI (`cpln` skill).\n\n## Failure catalog\n\n### Out of memory (OOMKilled)\n\n`memory` is a hard cap per container, sidecars included: app, runtime, GC, and buffers together. Usual culprits: Java without `-Xmx`, Node without `--max-old-space-size`, Python loading large datasets. Capacity AI can downsize a spiky workload too far; set `minMemory` as a floor.\n\n**Fix:** measure with `query_metrics`, then raise `memory`, and raise `cpu` with it: memory in MiB stays within 8 times CPU in millicores, so the default `50m` caps memory at 400Mi.\n\n### Image pull failures (`ImagePullBackOff`, `ErrImagePull`)\n\nCheck the reference form, the architecture (`linux/amd64` on managed locations; BYOK allows more), and the pull secret: a private external registry needs a secret of type `docker`, `ecr`, or `gcp` in the GVC's `pullSecretLinks`, created with `create_secret` and `values: \"user\"`, attached with `update_gvc`. Org `//image/` images need none. Detail: `image` skill.\n\n### Secret access failures\n\nEmpty env vars or secret errors in events mean the identity, `reveal` policy, or reference is missing or wrong. `grant_workload_secret_access` fixes the identity and policy in one call; reference formats per type: `setup-secret` skill.\n\n### Healthy but 502 or 503: port mismatch\n\n- The spec port must match what the process listens on; compare the spec with the startup logs.\n- On serverless the runtime injects `PORT` and rejects a `PORT` env var that differs from the exposed port.\n- Serverless exposes exactly one port (TCP needs a dedicated load balancer); standard and stateful expose zero or more; cron serves nothing.\n- Blocked ports cannot be bound and are invalid for TCP probes: 8012, 8022, 9090, 9091, 15000, 15001, 15006, 15020, 15021, 15090, 41000.\n\n### Firewall blocking traffic\n\nFix with `update_workload`: `external.inboundAllowCIDR` for inbound, `external.outboundAllowCIDR` or `outboundAllowHostname` for outbound (hostname rules reach only ports 80, 443, and 445), and `internal.inboundAllowType` or `allow_workload_access` for other workloads. Detail: `firewall-networking` skill.\n\n### Health-check failures\n\nServerless gets TCP readiness and liveness on the container port; standard, stateful, and cron get none. For a slow start raise `initialDelaySeconds` (0 to 600); for an over-eager probe raise `periodSeconds` (1 to 600) or `timeoutSeconds` (1 to 600, default 1); an HTTP path must return 200 to 399. A readiness failure takes the replica out of rotation and pauses the rollout; a liveness failure restarts it. An autoscaled workload without a real readiness probe takes traffic before it is ready and returns 502 on scale-up: add an `httpGet` readiness probe. Detail: `workload-security` skill.\n\n### Resources and Capacity AI\n\n- CPU and memory must fit the org quota; `maxScale` times the per-replica resources is enforced at scheduling.\n- Capacity AI is off with the `cpu` metric or multi-metric scaling; check the stored `spec.defaultOptions.capacityAI` first. On cron a new reservation lands at the next run.\n- Stateful sizing: `minCpu` to `cpu` at most 4 to 1 and 4000m apart; `minMemory` to `memory` at most 4 to 1 and 4096Mi apart.\n- Ephemeral storage is 1 GB per CPU core, at least 1 GB; exceeding it replaces the replica.\n\n### Container will not start\n\n- UID 1337 is the mesh proxy's: a container running as it bypasses the sidecar. Set `runAsUser` to another UID from 1 to 65534; root is rejected.\n- Reserved container names: `istio-proxy`, `queue-proxy`, `istio-validation`, and any starting with `cpln-` or `debugger-`.\n- Env names starting with `CPLN_`, and `K_SERVICE`, `K_CONFIGURATION`, `K_REVISION`, are rejected; each value is at most 4096 characters.\n- `spec.defaultOptions.suspend: true` stops the workload: clear it, or `cpln workload start WORKLOAD --gvc GVC`.\n\n### Autoscaling misconfiguration\n\nServerless has no `latency` or multi-metric scaling; standard and stateful have no `concurrency`; cron has no autoscaling. Scale to zero: serverless with `rps` or `concurrency`; standard and stateful only with `metric: keda`; never cron. 502 on scale-up is the readiness probe above. Detail: `autoscaling-capacity` skill.\n\n### Failures during deploys or scale-down\n\n- If `sleep` is missing from any container, every container is killed at once with no drain; distroless images often lack it (`which sleep`). Add `sleep` or a custom `preStop`.\n- A failing custom `preStop` in any container kills all of them.\n- After the preStop (default `sleep 45`) the container gets SIGTERM, then SIGKILL once `terminationGracePeriodSeconds` (default 90; at most 900 without the `cpln/relaxGracePeriodMax` tag) elapses. An app that ignores SIGTERM drops requests.\n\nSequence and rollout options: `workload-security` skill.\n\n### Volume mount failures\n\n- Secret volumes need the identity and `reveal` policy.\n- Cloud volumes (S3, GCS, Azure Blob and Files) need an identity with cloud access and outbound firewall to the provider hosts (`*.amazonaws.com`, `*.googleapis.com`, `*.blob.core.windows.net`, `*.file.core.windows.net`, `*.azure.com`). Only identity auth works; embedded keys do not. Read-only except Azure Files.\n- `filesystemGroupId` defaults to 0 (root) when unset: set it from 1 to 65534 for a non-root app.\n\nVolume sets: `stateful-storage` skill.\n\n### Service-to-service failures\n\n- The target's internal firewall must admit the caller (`allow_workload_access`); listing a workload needs `view` on it.\n- Call `http://WORKLOAD.GVC.cpln.local:PORT` over plain HTTP. An omitted port means the target's first container port; only listed ports are reachable.\n- Cross-GVC calls may span locations and pay egress.\n\n### Dedicated load balancer and domains\n\n- TCP needs a dedicated load balancer on the GVC plus a Domain with a TCP port; default endpoints serve HTTP, HTTP2, and gRPC only.\n- Enabling or disabling a dedicated load balancer causes a brief DNS propagation outage and per-location charges.\n- Behind a custom domain, a serverless workload receives the canonical endpoint as `Host` (the custom domain arrives in `X-Forwarded-Host`); standard and stateful receive the custom domain.\n- The domain port protocol must match the container's: HTTP2 fronts HTTP2 or gRPC, HTTP fronts HTTP.\n\nDetail: `domain` and `ipset-load-balancing` skills.\n\n## Fix within the schema's limits\n\nA fix the validator rejects wastes a round trip. Before a resource or option change, check:\n\n- Memory in MiB at most 8 times CPU in millicores; CPU at least 25m; memory at least 32Mi; `minCpu` and `minMemory` never above `cpu` and `memory`.\n- `runAsUser` and `filesystemGroupId` from 1 to 65534.\n- `terminationGracePeriodSeconds` at most 900, unless the `cpln/relaxGracePeriodMax` tag is set.\n- `capacityAI` is rejected with `metric: cpu` and with a GPU container.\n- Standard and stateful `minScale: 0` needs `metric: keda`; cron and vm cannot scale to zero.\n- A metric outside the type's list is rejected.\n\nA restart without a spec change (fresh secret values, wedged replicas) is `restart_workload`. After a fix, `diagnose_workload` confirms the symptom cleared.\n"
}

SHA-256 of public snapshot: 34eb323cea5dd438f7221d178fad9d2caedb2b60019aa44b53dd1f21b525908f