Home › DevOps › Control-M Job Stuck in Wait Condition: Fix Condition Dates & ODAT
Intermediate 3 min · October 30, 2026

Control-M Job Stuck in Wait Condition: Fix Condition Dates & ODAT

Control-M job stuck in wait condition? Learn how condition dates and ODAT cause it—and the exact steps to resolve it fast in production.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
✓ Production
production tested
October 30, 2026
last updated
2,099
articles · all by Naren
Before you start⏱ 25 min
  • ✓Basic familiarity with Control-M job scheduling and the Planning domain
  • ✓Access to Control-M Monitoring and Tools (or the AAPI/CLI)
  • ✓Understanding of Control-M conditions (add/delete) and job dependencies
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
⚡Quick Answer

Check the condition's date in the job's wait-for list versus the condition's actual date in the Conditions manager. If the job waits for COND-0101 but the condition was added as COND-0102, it will never trigger. Fix by adding the correct condition or adjusting the job's ODAT.

✦ Definition~90s read
What is Control-M Job Stuck in Wait Condition?

A Control-M job stuck in wait condition means its prerequisite conditions aren't satisfied. Condition dates define when a condition is valid (date+name), and ODAT (Order Date) is the scheduled date used to evaluate conditions. Mismatched dates cause the job to wait indefinitely.

★
Imagine a relay race where the baton is only valid on a specific day.
Plain-English First

Imagine a relay race where the baton is only valid on a specific day. If you're waiting for yesterday's baton, you'll stand there forever. Control-M conditions work the same way—each condition has a date stamp, and your job only accepts the baton if the dates match.

Your job's been sitting in Wait Condition for six hours, and the upstream job finished successfully. You've checked the logs, the dependencies, the agent—everything looks fine. But it's not. The condition date is wrong, and you're about to learn why.

This is the silent killer of Control-M schedules: condition dates and ODAT. They don't throw errors. They don't alert. They just make your job wait forever, and you only find out when the SLA is blown. I've seen a payments batch stuck for 12 hours because someone manually added a condition with today's date instead of the job's ODAT.

By the end of this, you'll know exactly how to diagnose a stuck job in under two minutes, fix condition date mismatches, and design your flows so this never happens again. No fluff—just the production patterns that work.

Why Condition Dates Exist: The Problem They Solve

Before condition dates, Control-M conditions were global and date-agnostic. If a job added FILE_READY, any job waiting for FILE_READY would trigger—even if it was from a different day's run. This caused chaos in daily batches: a job from Monday would trigger Tuesday's downstream jobs, leading to duplicate processing and data corruption.

Condition dates solve this by scoping conditions to a specific date (usually the ODAT). When a job adds a condition, it stamps it with its ODAT. When a job waits for a condition, it only accepts conditions with a matching date. This ensures that Monday's conditions only trigger Monday's jobs.

The trade-off? If dates don't match—due to manual reruns, timezone issues, or misconfiguration—jobs wait forever. There's no error, no warning. Just silence.

condition_date_example.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// io.thecodeforge — DevOps tutorial

// Job A (upstream) adds condition with its ODAT
// In Control-M job definition:
// On Do Notify: Add Condition PAYMENTS_READY
// (Control-M automatically appends ODAT to condition name)

// Job B (downstream) waits for condition with its ODAT
// In Control-M job definition:
// Wait For: PAYMENTS_READY
// (Control-M automatically appends ODAT to condition name)

// If Job A ODAT = 0101, condition becomes PAYMENTS_READY-0101
// If Job B ODAT = 0102, it waits for PAYMENTS_READY-0102
// Mismatch = Job B waits forever
Output
Job B remains in Wait Condition. No error. Condition PAYMENTS_READY-0101 exists but Job B expects PAYMENTS_READY-0102.
⚠ Production Trap:
Manual condition additions default to today's date, not the job's ODAT. Always specify the date explicitly: ctmcontb -ADD <cond> <date>.

ODAT: The Anchor for All Condition Dates

ODAT (Order Date) is the scheduled date of a job order. It's not the actual run date—it's the date the job is supposed to run for. When you schedule a job daily, each day's order gets an ODAT. All conditions added or waited for by that job use its ODAT.

Why does this matter? Because reruns and manual orders can have different ODATs. If you manually order a job for yesterday's date, its ODAT is yesterday, and it will add/wait for conditions with yesterday's date. If the upstream job ran today, its conditions have today's date—mismatch.

This is the #1 cause of stuck jobs after a rerun. The fix is to ensure all jobs in a flow share the same ODAT, or to manually adjust conditions.

odat_mismatch.devopsDEVOPS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// io.thecodeforge — DevOps tutorial

// Scenario: Daily batch scheduled for 2024-01-02
// Upstream job UPSTREAM_JOB has ODAT 20240102
// Downstream job DOWNSTREAM_JOB has ODAT 20240102

// Upstream adds condition: FILE_READY-0102
// Downstream waits for: FILE_READY-0102
// Match! Downstream runs.

// Now, operator manually reruns DOWNSTREAM_JOB for 2024-01-01 (ODAT 20240101)
// Downstream now waits for: FILE_READY-0101
// But upstream only added FILE_READY-0102
// Downstream stuck.

// Fix: Manually add condition with correct date:
// ctmcontb -ADD FILE_READY 0101
Output
After manual condition addition, DOWNSTREAM_JOB triggers immediately.
ODAT Mismatch Resolution
IfJob stuck after manual rerun
→
UseCheck ODAT of both jobs; add missing condition with downstream's ODAT
IfJob stuck after schedule change
→
UseVerify all jobs in flow have same ODAT; use 'Set ODAT' in job properties
IfJob stuck after timezone change
→
UseEnsure Control-M server and jobs use consistent timezone; check ODAT calculation

How to Diagnose a Stuck Job in Under 2 Minutes

Forget digging through logs. The fastest way is to compare what the job waits for with what conditions exist. Use the Control-M CLI or GUI.

Step 1: Get the job's wait-for conditions and ODAT. Step 2: List all conditions and their dates. Step 3: Spot the mismatch. That's it.

Here's the exact command sequence I use in production.

diagnose_stuck_job.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Step 1: Get job details including ODAT and wait-for conditions
# Replace JOB_NAME with your stuck job
ctmwhy JOB_NAME | grep -E 'ODAT|Wait For'

# Example output:
# ODAT: 0102
# Wait For: PAYMENTS_READY

# Step 2: List all conditions matching the name
ctmcontb -LIST PAYMENTS_READY "*"

# Example output:
# PAYMENTS_READY-0101  Added by UPSTREAM_JOB

# Step 3: Compare dates
# Job expects PAYMENTS_READY-0102 but only PAYMENTS_READY-0101 exists
# Fix: Add condition with correct date
ctmcontb -ADD PAYMENTS_READY 0102
Output
Job triggers immediately after condition addition.
💡Senior Shortcut:
Use ctmpsm with no arguments to open the interactive menu and browse active jobs, or check the Monitoring domain in the Control-M web interface for a compact view including ODAT and conditions.

The Manual Condition Trap: Why Adding Conditions Often Fails

You see the job waiting, you add the condition manually, and... nothing. The job still waits. Why? Because you added the condition with today's date, not the job's ODAT.

Control-M's ctmcontb -ADD <cond> <date> takes the date explicitly (MMDD), so if you pass today's date instead of the job's ODAT, your condition is useless. If the job's ODAT is yesterday (due to a rerun), always specify the date explicitly.

This is the mistake that cost one team 6 hours of debugging. Don't be them.

manual_condition_fix.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# WRONG: Adds condition with today's date
ctmcontb -ADD PAYMENTS_READY $(date +%m%d)

# RIGHT: Add condition with job's ODAT
# First, read the job's order date (ODAT) from its Job Properties in the Monitoring domain, e.g. 0102
ODAT=0102
# Then add condition with that date
ctmcontb -ADD PAYMENTS_READY $ODAT

# Verify condition was added
ctmcontb -LIST PAYMENTS_READY "*"
Output
Condition PAYMENTS_READY-0102 added. Job triggers.
⚠ Never Do This:
Never add conditions without specifying the date. It's the #1 cause of 'I added the condition but the job still waits' tickets.

Designing Flows That Don't Get Stuck: Best Practices

The best fix is prevention. Design your flows so condition dates always align. Use these patterns:

  1. Use relative dates in conditions: Instead of hardcoding dates, use Control-M's built-in date variables (e.g., %%ODAT) so conditions automatically match the job's ODAT.
  2. Avoid manual condition additions: Automate condition management via the API or scripts that pull the correct ODAT.
  3. Monitor condition mismatches: Set up alerts for jobs in Wait Condition longer than expected. Use Control-M's SLA management.
  4. Standardize ODAT across flows: When rerunning, rerun the entire flow with the same ODAT, not individual jobs.
relative_condition.devopsDEVOPS
1
2
3
4
5
6
7
8
// io.thecodeforge — DevOps tutorial

// In Control-M job definition, use %%ODAT in condition names
// Upstream job: Add Condition FILE_READY-%%ODAT
// Downstream job: Wait For FILE_READY-%%ODAT

// This ensures both use the same ODAT automatically.
// No manual date management needed.
Output
Conditions always match because both jobs use their own ODAT, which is the same for the flow.
Prevention Strategy Selection
IfNew flow design
→
UseUse %%ODAT in all condition names
IfExisting flow with hardcoded dates
→
UseRefactor to use %%ODAT; test with reruns
IfFrequent manual reruns
→
UseImplement a rerun script that sets ODAT consistently across all jobs

When to Use ODAT Override (And When It Bites You)

ODAT override lets you force a job to use a specific date, regardless of schedule. It's powerful for backfills and reruns, but it's also a common source of stuck jobs.

Use it when: you need to reprocess data for a specific date, or when recovering from a failure that requires a different date context.

Don't use it when: you're just trying to 'make the job run now'—that's what manual ordering is for. ODAT override changes the condition dates, which can break downstream dependencies if not coordinated.

Always communicate ODAT overrides to the team. A silent override can cause hours of confusion.

odat_override.shBASH
1
2
3
4
5
6
7
8
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Force job to run with ODAT 0102 (MMDD)
ctmorder -FOLDER FOLDER_NAME -NAME JOB_NAME -ODATE 0102 -FORCE y

# This job will now add/wait for conditions with date 0102
# Ensure upstream jobs also use 0102, or manually add conditions
Output
Job ordered with ODAT 20240101. Conditions will use that date (MMDD, e.g. 0101).
🔥Interview Gold:
ODAT override is not the same as manual order. Manual order uses the current schedule's ODAT; override forces a specific date. Know the difference—it's a common interview question.

Real-World Pattern: Handling Late-Arriving Files with Condition Dates

A common scenario: a file arrives late, and you need to trigger a job that's already past its scheduled time. If you just add the condition with today's date, it might not match the job's ODAT (which could be yesterday if the job was scheduled for yesterday but hasn't run yet).

The pattern: use a condition with a relative date that matches the job's ODAT. Or, use a file watcher job that adds the condition with the correct ODAT.

Here's how a media company handles late video files: they have a file watcher job that runs every hour, checks for files, and adds a condition with the current ODAT. The downstream processing job waits for that condition. If the file arrives late, the watcher adds the condition with the correct date, and the job triggers.

file_watcher.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# File watcher job: runs hourly, checks for new files
# If file exists, add condition with current ODAT

FILE_PATH="/data/incoming/video_$(date +%Y%m%d).mp4"
if [ -f "$FILE_PATH" ]; then
  # Get current ODAT from Control-M environment
  # In a real job, ODAT is available as %%ODAT
  ODAT=$(date +%m%d)  # Simplified for example
  ctmcontb -ADD VIDEO_READY $ODAT
  echo "Added condition VIDEO_READY-$ODAT"
else
  echo "File not found: $FILE_PATH"
fi
Output
Condition VIDEO_READY-0102 added. Downstream job triggers.

The Dark Side: When Condition Dates Cause Silent Failures

Condition dates are great for isolation, but they can also cause silent failures that are hard to debug. For example, if a job adds a condition with a date that no downstream job expects, the condition sits there forever, consuming resources and confusing operators.

Another issue: conditions with dates in the past can accumulate. Control-M doesn't automatically clean them up unless you configure retention. Over time, the Conditions manager becomes cluttered, and finding the right condition becomes a nightmare.

Mitigation: Set up regular cleanup of old conditions. Use Control-M's condition retention policies or a scheduled job that deletes conditions older than X days.

cleanup_conditions.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# io.thecodeforge — DevOps tutorial

# Delete conditions older than 7 days
# Use with caution: ensure no jobs are waiting for these conditions

CUTOFF_DATE=$(date -d '7 days ago' +%m%d)
ctmcontb -LIST "*" "*" | while read line; do
  COND_NAME=$(echo $line | awk '{print $1}')
  COND_DATE=$(echo $line | awk '{print $2}')
  if [ ! -z "$COND_DATE" ] && [ "$COND_DATE" -lt "$CUTOFF_DATE" ]; then
    ctmcontb -DELETE $COND_NAME $COND_DATE
    echo "Deleted $COND_NAME $COND_DATE"
  fi
done
Output
Deleted conditions older than 7 days.
⚠ Production Trap:
Never delete conditions without checking if any jobs are waiting for them. Use ctmcontb -LIST "" "" to see wait-for conditions first.
● Production incidentPOST-MORTEMseverity: high

The 12-Hour Payments Batch That Never Ran

Symptom
The nightly payments batch job sat in Wait Condition from 2 AM to 2 PM. Upstream jobs all showed Completed. No errors in logs.
Assumption
The team assumed the upstream job failed to add the condition, so they manually added it—but the job still didn't run.
Root cause
The upstream job added condition PAYMENTS_READY with date 0101 (its ODAT), but the downstream job had ODAT 0102 (due to a manual rerun). The condition dates didn't match, so the downstream job ignored it.
Fix
Use ctmcontb -LIST PAYMENTS_READY "*" to confirm ODAT. Then add condition with the correct date: ctmcontb -ADD PAYMENTS_READY 0102. Job triggered immediately.
Key lesson
  • Always check ODAT before manually adding conditions—a date mismatch will make you look like an idiot for hours.
Production debug guideSystematic recovery paths for the failure modes engineers actually hit.4 entries
Symptom · 01
Job stuck in Wait Condition, upstream job completed successfully
→
Fix
1. Run ctmwhy <orderno> to see why the job is waiting and which in-condition it expects. 2. Run ctmcontb -LIST <condition> "*" to see existing conditions and dates. 3. Compare dates; if mismatch, add condition with correct date: ctmcontb -ADD <condition> <ODAT>.
Symptom · 02
Condition exists but job still waits
→
Fix
1. Verify condition date matches job's ODAT exactly. 2. Check for typos in condition name (case-sensitive). 3. Ensure condition is not marked as 'deleted' or 'consumed'.
Symptom · 03
Job runs but downstream jobs don't trigger
→
Fix
1. Check if the job adds conditions with the correct date. 2. Use ctmcontb -LIST "" "" to see added conditions. 3. If date is wrong, manually add correct condition with ctmcontb -ADD <cond> <date> or rerun job with correct ODAT.
Symptom · 04
Manual condition addition doesn't unblock job
→
Fix
1. Confirm you're adding the condition with the exact date the job expects. 2. Use ctmcontb -ADD <cond> <date> with the job's ODAT. 3. Check Control-M logs for condition add errors.
★ Control-M Job Stuck in Wait Condition: Condition Dates and ODAT Explained Triage Cheat SheetFirst-response commands for when things go wrong — copy-paste ready.
Job in Wait Condition, no obvious reason−
Immediate action
Check job's ODAT and wait-for conditions
Commands
ctmwhy <orderno>
ctmcontb -LIST <condition_name> "*"
Fix now
If dates mismatch, add condition with job's ODAT: ctmcontb -ADD <cond> <ODAT>
Condition added but job still waits+
Immediate action
Verify condition date matches job's ODAT
Commands
ctmpsm (no arguments, interactive menu) or the Monitoring domain in the Control-M web interface
ctmcontb -LIST <condition_name> "*"
Fix now
Delete wrong condition and add correct one: ctmcontb -DELETE <name> <wrong_date> then ctmcontb -ADD <name> <correct_date>
Downstream jobs not triggering after upstream completes+
Immediate action
Check conditions added by upstream job
Commands
ctmcontb -LIST <upstream_job> "*"
ctmcontb -LIST <condition_name> "*"
Fix now
If condition date is wrong, manually add correct condition or rerun upstream with correct ODAT
Job stuck after manual rerun+
Immediate action
Check ODAT of rerun job vs. upstream
Commands
ctmpsm (no arguments, interactive menu) or the Monitoring domain in the Control-M web interface; to check a job's ODAT, use ctmwhy <orderno>
ctmpsm (no arguments, interactive menu) or the Monitoring domain in the Control-M web interface to browse active jobs; ctmwhy <orderno> to see why a job is waiting
Fix now
Align ODATs: rerun upstream with same ODAT or add condition manually with downstream's ODAT
Feature / AspectCondition Dates (ODAT-based)Global Conditions (No Date)
IsolationPer-day isolation; prevents cross-day triggersGlobal; any job can trigger any other
Risk of stuck jobsHigh if dates mismatchLow; but risk of duplicate triggers
Use caseDaily batches, date-sensitive flowsSimple, non-date-sensitive dependencies
Manual interventionRequires date-aware additionsSimple add/delete
CleanupNeeds retention policyManual cleanup only
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
diagnose_stuck_job.shctmwhy JOB_NAME | grep -E 'ODAT|Wait For'How to Diagnose a Stuck Job in Under 2 Minutes
manual_condition_fix.shctmcontb -ADD PAYMENTS_READY $(date +%m%d)The Manual Condition Trap
odat_override.shctmorder -FOLDER FOLDER_NAME -NAME JOB_NAME -ODATE 0102 -FORCE yWhen to Use ODAT Override (And When It Bites You)
file_watcher.shFILE_PATH="/data/incoming/video_$(date +%Y%m%d).mp4"Real-World Pattern
cleanup_conditions.shCUTOFF_DATE=$(date -d '7 days ago' +%m%d)The Dark Side

Key takeaways

1
Condition dates are scoped to ODAT—if they don't match, jobs wait forever with no error.
2
Always specify the date when manually adding conditions
ctmcontb -ADD <cond> <date> (date is MMDD).
3
Use %%ODAT in condition names to automate date matching and prevent mismatches.
4
ODAT override is powerful but dangerous—coordinate across the flow or you'll create stuck jobs.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
How does Control-M handle condition dates when a job is manually rerun w...
Q02SENIOR
When would you choose global conditions over date-based conditions in Co...
Q03SENIOR
What happens if you add a condition with a date that no job is waiting f...
Q04JUNIOR
What is ODAT in Control-M?
Q05SENIOR
A job is stuck in Wait Condition after a successful upstream run. Walk m...
Q06SENIOR
How would you design a Control-M flow to handle late-arriving files with...
Q01 of 06SENIOR

How does Control-M handle condition dates when a job is manually rerun with a different ODAT?

ANSWER
The job uses its new ODAT for all condition additions and waits. If upstream jobs ran with a different ODAT, their conditions won't match, causing the job to wait indefinitely. Mitigation: ensure all jobs in the flow share the same ODAT or manually add conditions with the correct date.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Why is my Control-M job stuck in wait condition even though the upstream job succeeded?
02
What's the difference between ODAT and run date in Control-M?
03
How do I add a condition with a specific date in Control-M?
04
Can condition dates cause silent failures in Control-M?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
✓ Verified
production tested
October 30, 2026
last updated
2,099
articles · all by Naren
🔥

That's Control-M. Mark it forged?

3 min read · try the examples if you haven't

←
Previous
Control-M Tutorial for Beginners: Jobs, Folders, Conditions and the New Day
2 / 4 · Control-M
Next
Control-M Ended Not OK: Find the Real Failure and Rerun Safely
→