Slurm job failure modes: triage and fixes
Field guide to job states, exit codes, pending reasons, and the standard triage steps for Slurm jobs that won't start, won't finish, fail mid-run, or vanish from the queue.
help for the full list, or solutions for reference procedures. Simulated outputs do not validate your environment.A job didn't start, or it started and died, or it finished with exit code 1 and no output. Each has a different signature in Slurm's logs and a different first command to run. This page is the lookup table.
The job state machine
submission
│
▼
┌─────────┐ ┌─────────┐
│ PD │── start ──▶ │ R │── exit ──▶ ┌─────────┐
│ Pending │ │ Running │ │ CG │
└────┬────┘ └────┬────┘ │ Compl- │
│ │ │ eting │
│ │ └────┬────┘
▼ ▼ │
┌─────────┐ ┌─────────┐ ▼
│ CA │ │ NF │ ┌─────────┐
│ Cancel │ │NodeFail │ │ CD │
└─────────┘ └─────────┘ │Compl- │
│ eted │
┌─────────┐ └─────────┘
│ F │
│ FAILED │
└─────────┘
┌─────────┐
│ TO │
│ Timeout │
└─────────┘
┌─────────┐
│ OOM │
└─────────┘
┌─────────┐
│ PR │
│ Preempt │
└─────────┘
The compact codes you see in squeue/sacct:
| Code | Name | Meaning |
|---|---|---|
PD | Pending | Queued, not yet running. The Reason field tells you why. |
R | Running | Allocated and executing. |
CG | Completing | Job exited; epilog is running; cgroup is being torn down. |
CD | Completed | Successful exit (exit code 0). |
F | Failed | Non-zero exit code from user task. |
TO | Timeout | Hit --time limit. |
OOM | Out-of-memory | Cgroup memory limit hit; OOM-killer fired. |
NF | NodeFail | A node went unhealthy mid-run. |
CA | Cancelled | scancel (by user or admin). |
BF | Boot fail | ResumeProgram failed to bring the node up. |
PR | Preempted | Higher-priority job kicked it. |
RV | Revoked | Federation revoked (rare in single-cluster SUNK). |
S | Suspended | scontrol suspend-ed manually. |
DL | Deadline | Hit --deadline=. |
Job stuck in PD: reading the Reason field
scontrol show job <id> prints a Reason= field. There are dozens; the high-frequency ones:
| Reason | Meaning | First fix |
|---|---|---|
Resources | No node-set currently satisfies the request. | sinfo, check IDLE counts vs request. |
Priority | A higher-priority job is ahead. | squeue --start -j <id> for ETA. |
Dependency | Waiting on --dependency=... jobs. | Check the parent jobs are still alive. |
JobHeldUser | User ran scontrol hold. | scontrol release <id>. |
JobHeldAdmin | Admin held it. | Check audit log for who/why before releasing. |
ReqNodeNotAvail | The exact node requested is DOWN/DRAIN. | sinfo -R, fix the node, or remove --nodelist. |
BadConstraints | The combination of features/GRES isn't satisfiable on any node. | See "BadConstraints pitfalls" below. |
AssocGrpJobsLimit | Association cap hit. | sacctmgr show assoc user=<u>, check GrpJobs. |
AssocGrpCPURunMinutesLimit | TRES-min limit. | Wait for usage decay or raise limit. |
QOSGrpJobsLimit | QoS cap hit. | sacctmgr show qos. |
QOSMaxJobsPerUserLimit | Per-user QoS cap. | --qos= switch or wait. |
Reservation | Waiting for a reservation window. | scontrol show res. |
PartitionDown | Partition STATE=DOWN. | scontrol update PartitionName=X State=UP. |
PartitionTimeLimit | --time= exceeds partition's MaxTime. | Lower --time or change partition. |
LaunchFailed | slurmd accepted but couldn't launch. | slurmd log on target node. |
BeginTime | --begin= is in the future. | Expected; or scontrol update Job=<id> StartTime=now. |
Licenses | License resource not free (rare in GPU clusters). | scontrol show licenses. |
BadConstraints pitfalls
The user requested something no node can ever satisfy:
--gres=gpu:8 --nodes=1on a partition where every node has 4 GPUs.--constraint=h100&ib400&local-nvmewhere no single node has all three features.--cpus-per-task=64 --ntasks-per-node=8 --gres=gpu:8on a 128-core node — that's 512 cores requested per node. Slurm catches this at submission usually, but staleslurm.confdata sometimes lets it through to PD.--mem=2Ton nodes withRealMemory=2000000(2000 GB ≠ 2 TB; off by ~7%). Use--mem-per-gpu=for portability.
scontrol show partition shows the partition's max nodes/CPUs/mem. Compare against the request. If still stuck: slurmctld -Dvvvv (debug) on a test instance and re-submit; the log will show the rejected node-by-node enumeration.
Job stuck in CG (Completing)
The job exited but Slurm hasn't released the resources. Causes:
- Epilog hung. The epilog script (e.g., NVIDIA persistence-mode reset, ACS re-disable, fabric counter capture) is blocked. Slurm waits up to
EpilogSlurmctldTimeoutthen kills it. - Container cleanup hung.
enrootcouldn't unmount the rootfs (busy file in /scratch held open by a stuck process). - Process stuck in
Dstate (uninterruptible). Often a stuck NFS/Weka I/O operation. The kernel won't kill it. - GPU stuck. A CUDA process won't release the device —
nvidia-smishowsprocesses: gpu busy.cgroupcleanup blocks waiting on the device handle.
UnkillableStepProgram is Slurm's escape hatch:
# slurm.conf
UnkillableStepProgram=/etc/slurm/unkillable.sh
UnkillableStepTimeout=180 # after 3 minutes, run UnkillableStepProgram
unkillable.sh typically: capture diagnostics (dmesg, ps, stuck-process stacks), then mark the node DRAIN so no new jobs land. Sample:
#!/bin/bash
# /etc/slurm/unkillable.sh
JOBID="$SLURM_JOB_ID"
NODE="$(hostname -s)"
LOGDIR="/var/log/slurm/unkillable"
mkdir -p "$LOGDIR"
{
echo "=== unkillable triggered at $(date -u) for job $JOBID on $NODE ==="
ps auxfwww
cat /proc/*/stack 2>/dev/null | head -200
nvidia-smi
dmesg | tail -100
} > "$LOGDIR/${JOBID}-${NODE}.log" 2>&1
scontrol update NodeName="$NODE" State=DRAIN Reason="unkillable job $JOBID"
After the node is drained: page on-call, reboot the node, then scontrol update Node=<n> State=RESUME.
Job in NF (NodeFail)
A node went unhealthy mid-run. Slurm marks the job NF and (depending on JobRequeue=1) requeues it. To attribute:
# Slurm's view of the job at the moment of failure
sacct -j <id> --format=JobID,JobName,State,ExitCode,DerivedExitCode,NodeList,Reason -p
# Per-node attribution: which node killed it?
scontrol show job <id> | grep -E "(NodeList|Reason)"
# Check the slurmd log on each listed node, around the failure timestamp
kubectl logs -n slurm <slurmd-pod-on-target> --since=1h | grep -i "node fail\|down\|unresponsive"
# Host-level: dmesg, GPU state, HW errors
ssh gpu-01 -- 'dmesg -T | tail -200; nvidia-smi -q | grep -E "Xid|ECC|Fallen"'
Common root causes:
- GPU Xid error → reboot may help; persistent → RMA. See NVIDIA GPU operator for the Xid catalog.
- IB link flap → check switch logs (see IB switches L2).
- Kubernetes evicted the slurmd Pod (memory pressure on the node, kubelet OOM).
- Underlying VM/host crashed (rare on bare metal, common on virtualized).
Job FAILED with exit code: walking the layers
sacct shows two exit codes:
$ sacct -j 12345 -o JobID,JobName,State,ExitCode,DerivedExitCode -p
JobID|JobName|State|ExitCode|DerivedExitCode
12345|train|FAILED|1:0|1:0
12345.batch|batch|FAILED|1:0|1:0
12345.0|torchrun|FAILED|1:0|1:0
Format is <exit>:<signal>. 1:0 = exit 1, no signal. 0:9 = killed by SIGKILL. 0:15 = SIGTERM (often the cgroup OOM-killer or scancel).
The investigation order, layer by layer:
- User script stdout/stderr —
--output=and--error=in the job script. Look for the actual Python traceback. - Slurm-side per-job log —
kubectl logs -n slurm slurmd-pod-on-the-node --since=1h | grep job=12345. Shows prolog/epilog output, container creation, signal exchange. - Pyxis log — pyxis prints to slurmd's stderr. Look for image pull failures, mount errors, container start refusal.
- Container OCI exit — when the container's CMD exits, that's the user's exit code passed up. If the user's script did
exit 1, that's where it came from. - Host kernel log —
dmesgfor OOM, NCCL fabric errors, CUDA driver messages, IB link state.
Job FAILED with OOM
Cgroup memory limit hit. Slurm marks State=OUT_OF_MEMORY (code OOM) and the dmesg on the node shows:
Memory cgroup out of memory: Killed process 1234567 (python) total-vm:...
The chain:
# Slurm's verdict
sacct -j <id> -o State,ExitCode,DerivedExitCode
# Cgroup events (cgroup v2)
ssh gpu-01 -- cat /sys/fs/cgroup/slurm/uid_5042/job_12345/memory.events
# oom 5
# oom_kill 5
# dmesg attribution
ssh gpu-01 -- dmesg -T | grep -A 30 "Killed process"
Common causes:
- User asked for
--mem=200Gbut the model needs 250G. Tell them--mem-per-gpu=is more portable. - Memory leak in the framework (PyTorch with grad-accumulate without zero-grad, etc.). Not your problem — refer to user.
RealMemory=inslurm.confis set above actual physical RAM. Slurm allocates "memory it doesn't have" and the system OOMs the slurmd cgroup itself before the user script. Verify:dmidecode -t memory | grep "Size:"vsRealMemory=inslurm.conf.
Job FAILED with NCCL errors
NCCL failures look like exit 1 with traceback containing NCCL error or unhandled cuda error. Distinguish two classes:
| Class | Signature | First action |
|---|---|---|
| Init failure | "NCCL WARN Cuda failure 'system not yet initialized'" or "Connection refused" during init | Check IB link state, NCCL_DEBUG=INFO logs, GPUDirect configuration |
| Mid-run failure | "NCCL WARN Network failure" partway through | IB link flap, fabric congestion, see NCCL |
Get NCCL debug from the user's job (or rerun with):
export NCCL_DEBUG=INFO
export NCCL_DEBUG_SUBSYS=ALL
export NCCL_DEBUG_FILE=/scratch/${USER}/nccl-%h-%p.log
Then grep -E "NCCL INFO|NCCL WARN|NCCL ERROR" on the per-host log files.
To attribute mid-run failures to a specific node:
sacct -j <id> -o NodeList- For each node, grep its slurmd log for the job's stop time
- Cross-reference with switch port-counter polls (if you have InfiniBand telemetry into Prometheus)
The NCCL doc covers the hierarchy. From SUNK's perspective: NCCL failures look like ordinary exit-1 jobs, the diagnosis is on the network side.
Prolog / epilog issues
Slurm runs scripts at well-defined points around a job:
| Hook | When | Runs as | Where it lives |
|---|---|---|---|
Prolog | Before job step starts, on each allocated node | root (slurmd context) | slurm.conf: Prolog= |
PrologSlurmctld | Before any allocation, on slurmctld | slurm user | slurm.conf: PrologSlurmctld= |
TaskProlog | Per-task, before user CMD | the user | slurm.conf: TaskProlog= |
TaskEpilog | Per-task, after user CMD | the user | slurm.conf: TaskEpilog= |
Epilog | After job completes, on each allocated node | root | slurm.conf: Epilog= |
EpilogSlurmctld | After job completes, on slurmctld | slurm user | slurm.conf: EpilogSlurmctld= |
Common prolog tasks on a GPU cluster:
- Disable PCIe ACS (re-disable, since reboots re-enable it). See GPUDirect.
- Set NVIDIA persistence mode:
nvidia-smi -pm 1. - Reset GPU clocks to default if a previous job changed them.
- Drop page cache if the previous job filled it:
sync; echo 3 > /proc/sys/vm/drop_caches. - Verify
dcgmi diag -r 1quick health check (skip if too slow).
Common epilog tasks:
- Capture per-job IB counters into the accounting log.
- Reset MIG mode if it was changed.
- Wipe
/tmpand/scratch/$USER/$SLURM_JOB_IDif the user didn't. - Detect and report Xid errors that occurred during the job.
A failing prolog manifests as Reason=Prolog in squeue, LaunchFailed in sacct, and a slurmd log line:
slurmd: error: prolog failed for job 12345 on gpu-01: exit 127
exit 127 is "command not found" — usually a missing binary in slurmd's PATH, or a bash interpreter mismatch (#!/bin/bash on a system with only /usr/bin/bash). The slurmd container in SUNK has its own PATH; don't rely on what's in your login shell.
A failing epilog rarely fails the job (the user has already exited successfully) but can leave the node DRAINed. sinfo -R will show the reason.
To debug: run the script manually with the same env Slurm gives:
sudo SLURM_JOB_ID=test SLURM_JOB_USER=alice SLURM_JOB_GID=5042 \
SLURM_JOB_UID=5042 /etc/slurm/prolog.sh
Job-array failures: retry only failed indices
sbatch --array=0-99 creates 100 jobs with IDs 12345_0 ... 12345_99. After a partial failure:
# Which indices failed?
sacct -j 12345 --format=JobID,State -p | awk -F'|' '$2=="FAILED" {print $1}' | \
sed 's/.*_//' | sort -n | paste -sd,
# 7,12,33,89
# Resubmit just those
sbatch --array=7,12,33,89 train-array.sh
# Or, if the original was --array=0-99%4 (max 4 concurrent),
# preserve the throttle:
sbatch --array=7,12,33,89%4 train-array.sh
For frameworks that natively understand Slurm array IDs (SLURM_ARRAY_TASK_ID), the user script can parameterize on the index. For ML training, this is usually a hyperparameter sweep — give them the failed-index list and let them rerun.
"Job not found" after submission
Submission appears to succeed but squeue -j <id> returns nothing.
Possibilities:
- slurmdbd connectivity —
sbatchreturned the jobid, slurmctld accepted it, but the asynchronous accounting write failed. Checkkubectl logs -n slurm slurmctld | grep -i dbd. WithAccountingStorageEnforce=safe, slurmctld will buffer; without, it just keeps going andsacctis wrong. - Job completed almost instantly —
squeueonly shows pending+running.sacct -j <id>sees historical jobs. - Dual-cluster federation confusion — the job ran on a different cluster. Use
sacct -M <cluster>to enumerate. MinJobAgepurged it — slurmctld purges completed jobs from in-memory queue afterMinJobAge(default 300s). Usesacct, notsqueue.
To debug submission failures (where sbatch itself returns non-zero):
sbatch -vvvv myjob.sh
# or
sbatch --test-only myjob.sh # validates without submitting
-vvvv shows the RPCs to slurmctld; useful for "Unable to contact slurm controller" diagnostics.
Diagnosis command quick reference
| Command | When |
|---|---|
squeue -u <user> | What's pending/running for this user. |
squeue -j <id> --start | Estimated start time. |
sprio -j <id> | Why this priority. |
scontrol show job <id> | Full job record, current Reason. |
sacct -j <id> --format=JobID,JobName,State,ExitCode,DerivedExitCode,NodeList,Reason -p | Historical record. |
sacct -j <id> -o all | Every column. Useful when you don't know what you're looking for. |
sacct -u <user> -S now-1day -X | All this user's jobs in last day, no array steps. |
sjstat | Cluster-wide queue summary (third-party but standard). |
sstat -j <id>.<step> | Live counters of a running step (CPU, mem, IO). |
scontrol show node <n> | Node state, current reason if DRAIN. |
scontrol show partition | Partition definitions. |
sinfo -R | All nodes with non-IDLE reason. |
sinfo -N -l | Per-node detailed view. |
scontrol diag | Scheduler health. |
For the long-running historical lookup:
sacct -S 2026-05-01 -E 2026-05-04 -u alice \
--format=JobID,JobName%30,Partition,Account,AllocNodes,AllocCPUS,AllocTRES%50,State,ExitCode,Start,End,Elapsed
Adjust column widths with %N. --format=...,AllocTRES%80 is essential for GPU jobs (otherwise the GPU count is truncated).
See also
- Slurm scheduling — why a job is in PD
- SUNK troubleshooting — when the K8s side breaks the job
- enroot + pyxis — container-side failure modes
- NCCL — multi-node communication failures
- DCGM — GPU Xid attribution
External: