Troubleshoot APM SSI on Kubernetes
Triggers
Invoke this skill when the user expresses intent to:
- Debug why a pod is not being instrumented
- Investigate why traces are not appearing in Datadog
- Diagnose admission webhook or init container injection failures
- Follow up on failed checks from
verify-ssi - Report that a specific service or pod has no traces
Do NOT invoke this skill if:
- SSI has not been enabled yet — run
enable-ssifirst
Prerequisites
- kubectl configured to target cluster —
kubectl config current-context
pup-cli: check, install, and authenticate
Claude runs
If not found, install it (OS-aware):
Claude runs
Check auth:
If not authenticated:
Claude runs
This opens a browser tab for OAuth. Complete the login there — Claude will continue once the command exits.
If no browser available: export DD_APP_KEY=<your-app-key>.
Context to resolve before acting
How SSI Works — Domain Knowledge
Read this before investigating. It gives you the mental model to reason about novel failures, not just known ones.
Injection chain:
- Admission webhook (registered by Cluster Agent) intercepts pod creation
- Webhook mutates the pod spec — adds a
datadog-lib-<language>-initinit container - Init container downloads the tracer library onto a shared volume
LD_PRELOADenv var is set pointing to the library.sofile- Application process loads the library automatically on startup via
LD_PRELOAD
What each diagnostic layer can see:
- pup — sees what Datadog's backend received. Blind to cluster-side injection failures. If pup shows no tracer telemetry for the service, the tracer was either never injected (cluster-side — confirm with the kubectl init-container check) or injected but unable to report yet (connectivity, DD_SITE, API key, or telemetry lag). Don't assume the cluster; cross-check kubectl.
- kubectl — sees cluster state. Blind to whether data reached Datadog. If kubectl shows the init container but pup shows no traces, the problem is post-injection.
What healthy looks like:
pup fleet tracers listshows the service as active, with the expected languagekubectl get pod -o jsonpath='{.spec.initContainers[*].name}'includesdatadog-lib-<language>-init
Known silent failures — SSI produces no error when these occur:
- Existing ddtrace or OTel instrumentation — SSI detects it and silently disables itself
- Unsupported runtime version — silently skipped
admission.datadoghq.com/enabled: "false"annotation — webhook skips the pod entirely- Pod not restarted after SSI enabled — injection happens at startup; existing pods keep running uninstrumented
- Pod in Agent namespace — SSI never instruments its own namespace
Reasoning shortcuts:
- No init container → webhook didn't fire → check: namespace targeting, pod-selector, opt-out annotation, webhook registration, pod not restarted
- Init container present + no traces → check whether the service is in
pup fleet tracers list: absent → tracer not reporting (Agent connectivity, DD_SITE mismatch, API key, blocked egress; or the tracer never loaded — existing ddtrace/OTel, unsupported runtime); present → reporting telemetry but spans not arriving (no traffic, sampling, ingestion/retention filter)
Step 1: Triage
Run all seven simultaneously and surface them back to the user as the diagnostics you're running. Everything after this is driven by what you find here. Resolve <NODE_HOSTNAME> from kubectl get pod <POD_NAME> -n <APP_NAMESPACE> -o jsonpath='{.spec.nodeName}' once you have a pod name; if no pod context yet, run the pup commands without --hostname first.
Claude runs
The last command confirms the Admission Controller webhook is registered cluster-wide — this is the precondition for SSI injection working at all and must be checked even when most other services are being instrumented (any deviation in one webhook config can silently skip a subset of pods).
pup apm troubleshooting list surfaces injection errors that Datadog's backend received from the cluster. These point to cluster-side mutation failures that may not be visible from kubectl describe alone. pup apm service-library-config get shows the runtime SDK config the tracer is operating under; unexpected values point to UST/config-propagation issues. This view does not list every setting for every SDK, so for a missing value check the pod env with the command in verify-ssi Step 3 before concluding the config did not propagate.
Presenting your findings (required)
Your final response is the deliverable — not your investigation transcript. It must include every diagnostic from this skill that you ran or that applies, each with its purpose and what you found. Three failure modes to avoid:
-
Running a check but not reporting it. If you ran
kubectl get mutatingwebhookconfigurations, the namespaceadmission.datadoghq.com/mutate-podslabel check, or any other command during investigation, state the command and its result in your response. A check you ran but didn't surface gives the reader nothing — and the namespace-label and webhook checks in particular must appear explicitly. -
Omitting the two required pup diagnostics. Every diagnosis must explicitly include these two commands, by name, for each affected service — they are mandatory triage output, not optional:
pup apm troubleshooting list --hostname <NODE_HOSTNAME>— surfaces injection errors Datadog received from the nodepup apm service-library-config get --service-name <SERVICE_NAME> --env <ENV>— shows the tracer's runtime SDK config
Run them if
pupis available; recommend them for the user to run if it isn't. Do not substitutepup traces searchfor these — it is a different check and does not satisfy the runbook. If you don't know<ENV>, state your assumed value and run the command anyway. -
Stopping at the first root cause. When multiple services are affected, investigate and report each one independently — they may have different causes — and give per-service remediation.
Step 2: State Your Hypotheses
Before investigating, explicitly state your ranked hypotheses based on triage output. Do not skip this step.
When the user reports multiple affected services in the same namespace, diagnose each independently. Two pods can fail injection for entirely different reasons (one opt-out annotation, one missing namespace label, one with pre-existing ddtrace). Do not assume a shared root cause — investigate each service's pod spec, annotations, and runtime separately and surface findings per-service.
State your top 1-3 hypotheses explicitly: "Based on triage, I think the most likely cause is X because Y."
Step 3: Investigate
Use only the tools relevant to your hypotheses. Each observation informs your next action.
Cluster-side investigation tools
Is the pod in the Agent namespace? SSI never instruments pods in the same namespace as the Datadog Agent.
Were pods restarted after SSI was enabled?
Confirm with the user before restarting. Tell the user: "Pods must be restarted for SSI to inject into them. I'll restart
<DEPLOYMENT_NAME>in<APP_NAMESPACE>. Ready to proceed?" Wait for confirmation.
Claude runs
Claude runs
The kubectl init-container check is the authoritative post-restart signal — the pod is injected the moment that init container appears. tracers list is telemetry-derived and lags, so don't read an empty result immediately after a restart as "injection didn't happen."
Does the namespace carry the Admission Controller opt-in label?
When the Admission Controller runs with mutateUnlabelled: false, injection happens only in namespaces explicitly labeled admission.datadoghq.com/mutate-pods=true. A namespace missing this label silently has SSI skipped for every pod in it — a common cause when most cluster services are instrumented but one namespace's services aren't.
Fix: label the namespace, then restart the affected deployments so the AC mutates them on pod recreate.
Is namespace targeting filtering the pod out?
Fix: update enabledNamespaces in datadog-agent.yaml.
Claude runs
Is a podSelector target filtering the pod out?
If targets with podSelector is configured, only pods whose labels match the selector are instrumented. Check whether the app pod's labels match any target:
Fix: add a matching label to the pod template, or broaden the podSelector, then apply and restart.
Is a pod annotation opting it out — or missing the AC's injection-success annotation? Two annotations to look for:
admission.datadoghq.com/enabled: "false"— explicit opt-out, AC skips the pod.admission.datadoghq.com/status: injected— set by the AC after successful mutation; its absence on a running pod is positive evidence the AC never mutated it.
Fix: remove an opt-out annotation from the Deployment pod template, then apply and restart.
Are the expected DD_* environment variables present in the running pod?
SSI injects DD_SERVICE, DD_ENV, DD_VERSION, DD_TRACE_*, and LD_PRELOAD into the container env when it mutates a pod. Their absence confirms the mutation did not run; their presence with unexpected values points to UST label mismatches or ddTraceConfigs issues.
Claude runs
Confirm with the user before restarting. Tell the user: "I need to restart
<DEPLOYMENT_NAME>in<APP_NAMESPACE>for this change to take effect. Ready to proceed?" Wait for confirmation.
Claude runs
Does the app have existing custom instrumentation? SSI silently disables itself when it detects existing tracer code. Scan source files for:
- Python:
import ddtrace,ddtrace.patch_all() - Node.js:
require('dd-trace'),DD.init() - Java:
GlobalTracer.register(,dd-java-agent - .NET:
Tracer.Instance,DD.Trace - Ruby:
require 'ddtrace',Datadog.configure - PHP:
DDTrace\
Also check dependency manifests: requirements.txt, package.json, Gemfile, pom.xml.
Fix: remove the import/package, rebuild image, reload into cluster, restart pod.
Is the base image Alpine (musl libc)?
K8s SSI injects LD_PRELOAD as an environment variable into the pod — it does not rely on /etc/ld.so.preload, so musl/Alpine images are supported. This is not a blocker for Kubernetes SSI.
Is the runtime version supported?
Verify against SSI compatibility matrix.
Is the admission webhook registered?
Did injection produce errors? Get the node hostname first, then query Datadog for injection errors:
Is the Agent sending data to Datadog?
Datadog-side investigation tools
Is the tracer reporting?
Does APM recognise the service?
What SDK configuration is the service running with?
Shows env vars the tracer is configured with (e.g. DD_TRACE_ENABLED, DD_SERVICE, DD_ENV, sampling rules). Empty output is expected if ddTraceConfigs was not set in enable-ssi; a populated output mismatching what was configured indicates the change didn't propagate.
Are traces arriving?
Which agent is the tracer connected to? Use if connectivity between tracer and Agent is suspected.
Step 4: Reflect Before Concluding
Before applying any fix, answer:
- What evidence confirms my hypothesis?
- What evidence would contradict it — and have I checked?
- Is there a simpler explanation I haven't considered?
If the conclusion doesn't hold up, return to Step 2 with new hypotheses. Keep iterating until you can defend the conclusion against all three questions.
Step 5: Fix
Apply the fix for the confirmed root cause. If the fix requires a code or Dockerfile change, rebuild and reload:
Claude runs
[DECISION: cluster type]
- kind (local): load the image into the cluster
Claude runs
- Registry-based: skip — image will be pulled on next deployment
Confirm with the user before restarting. Tell the user: "I need to restart
<DEPLOYMENT_NAME>in<APP_NAMESPACE>to apply the fix. Ready to proceed?" Wait for confirmation.
Claude runs
Step 6: Verify
Re-run triage to confirm the fix worked:
Claude runs
If traces are arriving — resolved (the service may take a minute to reappear in pup fleet tracers list; an empty tracers list immediately after the restart is expected telemetry lag, not a failure). Automatically proceed to onboarding-summary now — do not ask the user for permission.
ERROR: No traces arriving — return to Step 2 with the new triage data and form updated hypotheses.
Security constraints
- Never write a raw API key into any file or chat message
- Never run
kubectl deletewithout user confirmation - Never modify
admissionControllersettings directly docker pushto a registry always requires user confirmation


