Problem – Claude Container CrashLoop During Large‑Dataset Evaluation
In a Kubernetes‑based evaluation pipeline the anthropic/claude container repeatedly enters CrashLoopBackOff while processing a benchmark dataset that exceeds 10 k prompts (≈5 GB JSON). The pod never reaches the Running state long enough to complete the evaluation workflow.
Typical symptom log excerpt:
kubectl logs claude-eval-7f9c9d8c5b-abcde -c claude
...
2024-07-14T10:22:31.412Z ERROR OutOfMemoryError: Java heap space
...
2024-07-14T10:22:35.001Z INFO Shutting down inference engine
...
Pod status (after three restarts):
kubectl get pod claude-eval-7f9c9d8c5b-abcde -o wide
NAME READY STATUS RESTARTS AGE
claude-eval-7f9c9d8c5b-abcde 0/1 CrashLoopBackOff 3 6m
Common error messages observed:
- “Container terminated with exit code 137” – kernel OOM kill.
- “Readiness probe failed: HTTP probe failed with statuscode: 500” – inference latency > probe timeout.
- “java.lang.OutOfMemoryError: Java heap space”.
- “torch.cuda.OutOfMemoryError: CUDA out of memory” on GPU nodes.
Root Cause Analysis
The crash loop is usually the result of one or more of the following interacting factors:
- Unbounded input size. The Claude API enforces request size limits (see Claude API Reference). When the container receives a batch payload larger than the default
--max-input-bytes, the internal tokenizer allocates memory proportional to the token count. Large JSON files (5 GB) exceed the default limit, causing the Java wrapper to allocate more heap than the container permits. - Insufficient Kubernetes memory requests/limits. The Anthropic container guide (Docker Container Guide) recommends at least 8 Gi of RAM for batch evaluation. Pods with
resources.limits.memoryset to 4 Gi quickly hit the cgroup limit, leading to OOM kill (exit code 137). - Readiness probe timeout. The default probe expects a 2‑second response. Large inputs cause inference latency >300 s (see real incident “Readiness probe failed”); the probe fails, Kubernetes restarts the container.
- Concurrent request explosion. The flag
--max-concurrent-requestsdefaults to 5. When the evaluation script streams thousands of prompts without throttling, the container spawns many inference threads, inflating both CPU and memory pressure. - GPU memory leaks. On GPU‑enabled nodes, missing
torch.cuda.empty_cache()after each batch can accumulate GPU memory, eventually triggeringtorch.cuda.OutOfMemoryError(see GitHub issue OutOfMemoryError in Claude container on GPU nodes).
Investigation and Debugging Steps
1. Inspect pod events and OOM metrics
kubectl describe pod claude-eval-7f9c9d8c5b-abcde
Look for lines such as:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning OOMKilled 2m kubelet, node-1 Container claude was killed due to out of memory
2. Check container logs for Java or Python OOM traces
kubectl logs claude-eval-7f9c9d8c5b-abcde -c claude | grep -i outofmemory
3. Verify resource usage while a small batch runs
kubectl top pod claude-eval-7f9c9d8c5b-abcde
Compare memory usage against the resources.limits defined in the pod spec.
4. Capture the failing request size
# In the evaluation script, log the byte size of each batch payload
payload_bytes=$(jq -c . input_batch.json | wc -c)
echo "Batch payload size: ${payload_bytes} bytes"
5. Test readiness probe latency manually
curl -v http://:8080/healthz
# Measure response time; if >2s, increase probe timeout.
6. Reproduce OOM on a node with higher memory
kubectl run debug --image=busybox --restart=Never --rm -it -- \
sh -c "apk add --no-cache curl && curl http:///evaluate -d @large_batch.json"
Solution – Stabilizing Claude Evaluation Pods
1. Adjust Kubernetes resource requests/limits
Before (insufficient memory):
apiVersion: v1
kind: Pod
metadata:
name: claude-eval
spec:
containers:
- name: claude
image: anthropic/claude:latest
resources:
requests:
memory: "4Gi"
cpu: "2"
limits:
memory: "4Gi"
cpu: "4"
After (recommended for large batches):
apiVersion: v1
kind: Pod
metadata:
name: claude-eval
spec:
containers:
- name: claude
image: anthropic/claude:latest
env:
- name: JAVA_OPTS
value: "-Xmx6g -Xms6g"
resources:
requests:
memory: "12Gi"
cpu: "4"
limits:
memory: "12Gi"
cpu: "8"
# Enable swap on nodes that allow it (see Anthropic container guide)
securityContext:
privileged: true
2. Enforce input size limits
Add the flag --max-input-bytes=10485760 (10 MiB) to the container entrypoint, matching the API recommendation (API Reference).
# Dockerfile snippet
ENTRYPOINT ["java", "-jar", "/app/claude.jar", "--max-input-bytes=10485760"]
3. Enable request throttling
Set --max-concurrent-requests=2 to limit parallel inference threads.
# In the Helm values or pod spec
args:
- "--max-concurrent-requests=2"
4. Increase readiness/liveness probe timeouts
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 30 # increased from 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 60
periodSeconds: 20
timeoutSeconds: 30
5. Shard the dataset
Instead of a single 5 GB JSON file, split into ≤ 1 GB chunks using jq or a custom script, then invoke the container sequentially.
jq -c '.[]' large_dataset.json | split -l 10000 - dataset_part_
# Each part now contains 10k prompts, well within memory limits.
6. GPU memory cleanup (if using GPU nodes)
Insert torch.cuda.empty_cache() after each batch in the evaluation script.
import torch
def evaluate_batch(batch):
# ... inference logic ...
torch.cuda.empty_cache()
Verification – Confirming the Fix
- Redeploy the pod with the updated manifest.
- Run a controlled batch (e.g., 5 k prompts) and watch the pod stay
Runningfor the full duration. - Check logs for absence of OOM traces:
kubectl logs claude-eval-xxxx -c claude | grep -i outofmemory
# No output expected
kubectl get pod claude-eval-xxxx -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'
# Should output "True"
kubectl top pod claude-eval-xxxx
# Memory should stay below the limit (e.g., 10‑11 Gi of 12 Gi limit)
Prevention – Operational Guardrails
- Resource monitoring. Set Prometheus alerts on
container_memory_usage_bytesapproaching 90 % of the limit. - Dataset size validation. Add a CI step that rejects benchmark payloads exceeding
--max-input-bytes. - Probe tuning. Align
readinessProbe.timeoutSecondswith the 95th‑percentile inference latency measured in production. - Batch chunking policy. Enforce a maximum of 10 k prompts per batch (per Batch Evaluation Guide).
- Auto‑scaling. Configure a HorizontalPodAutoscaler that adds replicas when
cpu_utilizationormemory_utilizationexceeds 70 %.
Related Topic Hub: LLM Systems Troubleshooting Hub
FAQ
- Why does the container crash only when the dataset exceeds 10 k records?
Because each record adds to the tokenization buffer. Beyond the default--max-input-bytesthe Java heap grows past the pod’s memory limit, triggering an OOM kill. - How can I determine the exact byte size of a batch payload before sending it to Claude?
Usejq -c . batch.json | wc -cor, in Python,len(json.dumps(batch).encode('utf-8')). Compare against the--max-input-bytesvalue. - Is increasing the Kubernetes memory limit sufficient?
It prevents OOM kills but does not address the underlying API size limit or probe timeouts. Both the container flag and readiness probe must be tuned in tandem. - Can I keep the original large JSON file and avoid sharding?
Only if you raise--max-input-bytesto a value that still fits within the pod’s heap and you increase the Java heap (-Xmx) accordingly. This is risky; sharding is the recommended, production‑grade approach. - What should I monitor to catch similar failures early?
Set alerts on:container_memory_rss> 85 % of limit- Readiness probe failure count > 0
- Exit codes 137 or 1 in
kube_pod_container_status_last_termination_reason