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.

Try the commands on this page in the command emulator — type help for the full list, or solutions for copy-paste fix recipes.

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:

CodeNameMeaning
PDPendingQueued, not yet running. The Reason field tells you why.
RRunningAllocated and executing.
CGCompletingJob exited; epilog is running; cgroup is being torn down.
CDCompletedSuccessful exit (exit code 0).
FFailedNon-zero exit code from user task.
TOTimeoutHit --time limit.
OOMOut-of-memoryCgroup memory limit hit; OOM-killer fired.
NFNodeFailA node went unhealthy mid-run.
CACancelledscancel (by user or admin).
BFBoot failResumeProgram failed to bring the node up.
PRPreemptedHigher-priority job kicked it.
RVRevokedFederation revoked (rare in single-cluster SUNK).
SSuspendedscontrol suspend-ed manually.
DLDeadlineHit --deadline=.

Job stuck in PD: reading the Reason field

scontrol show job <id> prints a Reason= field. There are dozens; the high-frequency ones:

ReasonMeaningFirst fix
ResourcesNo node-set currently satisfies the request.sinfo, check IDLE counts vs request.
PriorityA higher-priority job is ahead.squeue --start -j <id> for ETA.
DependencyWaiting on --dependency=... jobs.Check the parent jobs are still alive.
JobHeldUserUser ran scontrol hold.scontrol release <id>.
JobHeldAdminAdmin held it.Check audit log for who/why before releasing.
ReqNodeNotAvailThe exact node requested is DOWN/DRAIN.sinfo -R, fix the node, or remove --nodelist.
BadConstraintsThe combination of features/GRES isn't satisfiable on any node.See "BadConstraints pitfalls" below.
AssocGrpJobsLimitAssociation cap hit.sacctmgr show assoc user=<u>, check GrpJobs.
AssocGrpCPURunMinutesLimitTRES-min limit.Wait for usage decay or raise limit.
QOSGrpJobsLimitQoS cap hit.sacctmgr show qos.
QOSMaxJobsPerUserLimitPer-user QoS cap.--qos= switch or wait.
ReservationWaiting for a reservation window.scontrol show res.
PartitionDownPartition STATE=DOWN.scontrol update PartitionName=X State=UP.
PartitionTimeLimit--time= exceeds partition's MaxTime.Lower --time or change partition.
LaunchFailedslurmd accepted but couldn't launch.slurmd log on target node.
BeginTime--begin= is in the future.Expected; or scontrol update Job=<id> StartTime=now.
LicensesLicense 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=1 on a partition where every node has 4 GPUs.
  • --constraint=h100&ib400&local-nvme where no single node has all three features.
  • --cpus-per-task=64 --ntasks-per-node=8 --gres=gpu:8 on a 128-core node — that's 512 cores requested per node. Slurm catches this at submission usually, but stale slurm.conf data sometimes lets it through to PD.
  • --mem=2T on nodes with RealMemory=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:

  1. Epilog hung. The epilog script (e.g., NVIDIA persistence-mode reset, ACS re-disable, fabric counter capture) is blocked. Slurm waits up to EpilogSlurmctldTimeout then kills it.
  2. Container cleanup hung. enroot couldn't unmount the rootfs (busy file in /scratch held open by a stuck process).
  3. Process stuck in D state (uninterruptible). Often a stuck NFS/Weka I/O operation. The kernel won't kill it.
  4. GPU stuck. A CUDA process won't release the device — nvidia-smi shows processes: gpu busy. cgroup cleanup 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:

  1. User script stdout/stderr--output= and --error= in the job script. Look for the actual Python traceback.
  2. Slurm-side per-job logkubectl logs -n slurm slurmd-pod-on-the-node --since=1h | grep job=12345. Shows prolog/epilog output, container creation, signal exchange.
  3. Pyxis log — pyxis prints to slurmd's stderr. Look for image pull failures, mount errors, container start refusal.
  4. 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.
  5. Host kernel logdmesg for 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=200G but 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= in slurm.conf is 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:" vs RealMemory= in slurm.conf.

Job FAILED with NCCL errors

NCCL failures look like exit 1 with traceback containing NCCL error or unhandled cuda error. Distinguish two classes:

ClassSignatureFirst action
Init failure"NCCL WARN Cuda failure 'system not yet initialized'" or "Connection refused" during initCheck IB link state, NCCL_DEBUG=INFO logs, GPUDirect configuration
Mid-run failure"NCCL WARN Network failure" partway throughIB 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:

HookWhenRuns asWhere it lives
PrologBefore job step starts, on each allocated noderoot (slurmd context)slurm.conf: Prolog=
PrologSlurmctldBefore any allocation, on slurmctldslurm userslurm.conf: PrologSlurmctld=
TaskPrologPer-task, before user CMDthe userslurm.conf: TaskProlog=
TaskEpilogPer-task, after user CMDthe userslurm.conf: TaskEpilog=
EpilogAfter job completes, on each allocated noderootslurm.conf: Epilog=
EpilogSlurmctldAfter job completes, on slurmctldslurm userslurm.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 1 quick 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 /tmp and /scratch/$USER/$SLURM_JOB_ID if 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:

  1. slurmdbd connectivitysbatch returned the jobid, slurmctld accepted it, but the asynchronous accounting write failed. Check kubectl logs -n slurm slurmctld | grep -i dbd. With AccountingStorageEnforce=safe, slurmctld will buffer; without, it just keeps going and sacct is wrong.
  2. Job completed almost instantlysqueue only shows pending+running. sacct -j <id> sees historical jobs.
  3. Dual-cluster federation confusion — the job ran on a different cluster. Use sacct -M <cluster> to enumerate.
  4. MinJobAge purged it — slurmctld purges completed jobs from in-memory queue after MinJobAge (default 300s). Use sacct, not squeue.

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

CommandWhen
squeue -u <user>What's pending/running for this user.
squeue -j <id> --startEstimated 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 -pHistorical record.
sacct -j <id> -o allEvery column. Useful when you don't know what you're looking for.
sacct -u <user> -S now-1day -XAll this user's jobs in last day, no array steps.
sjstatCluster-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 partitionPartition definitions.
sinfo -RAll nodes with non-IDLE reason.
sinfo -N -lPer-node detailed view.
scontrol diagScheduler 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

External: