Control-M Ended Not OK: Find the Real Failure and Rerun Safely
Control-M Ended Not OK? Trace the real failure from job log to sysout, then rerun safely without duplicating data or breaking dependencies.
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
- ✓Basic Control-M job scheduling concepts (jobs, folders, dependencies)
- ✓Access to Control-M Monitoring and the job's log/sysout
- ✓Familiarity with your shell or scripting language used in job commands
To find the real failure, open the job's log in Control-M Monitoring, check the sysout for the exact error, and verify any post-processing conditions. To rerun safely, first confirm idempotency, clear the failure condition, and use 'Rerun' from the job's context menu—only rerun if you know the job is safe to repeat.
Think of Control-M as a restaurant kitchen expediter. When a dish comes back to the pass marked 'not OK', the expediter doesn't just shout 'cook it again'—they check whether the steak was raw, the sauce was cold, or the plate was wrong. If you rerun without reading the ticket, you might send out the same raw steak, or worse, double-charge the customer. The 'Ended Not OK' is the ticket; the real failure is why the dish failed.
Most Control-M operators treat 'Ended Not OK' as a button to press, not a symptom to read. That habit is how a one-minute network blip becomes a three-hour data reconciliation. The status tells you a job failed; it does not tell you why, and rerunning blindly is how you duplicate payments, double-send emails, or corrupt a downstream table. The real failure is always in the log, the sysout, or a condition—never in the status itself. After this article, you'll be able to trace any Ended Not OK to its root cause in under five minutes, decide whether a rerun is safe, and execute the rerun without breaking dependencies or duplicating side effects.
Why 'Ended Not OK' Is a Symptom, Not a Diagnosis
Control-M sets Ended Not OK when a job's command returns a non-zero exit code, a post-processing condition fails, or the output matches an On/Do rule that sets the status to NOTOK. That status is a signal, not a root cause. The real failure is always in one of three places: the job's sysout (application output), the Control-M log (scheduler-level errors), or a condition that never arrived. Treating the status as the problem is like treating a fever as the disease—you'll keep giving aspirin while the infection spreads. The first rule of production triage: open the sysout before you touch the rerun button.
Reading the Sysout: Where the Real Error Lives
Control-M's job log shows scheduler events—when the job started, which node it ran on, and the exit code. The sysout (View Output) contains what your script actually printed. That's where you'll find the stack trace, the SQL error, or the 'Permission denied'. The classic mistake is staring at the Control-M log and seeing only 'Command failed with exit code 1'—that's the scheduler telling you the script failed, not why. Always open the sysout first. If the sysout is empty, the job failed before producing output—check the command path, environment variables, and user permissions.
Conditions: The Silent Failure Mode
Not all Ended Not OK statuses come from a non-zero exit code. A job can fail because a condition it waited for never arrived, or because a post-processing condition check failed. These are silent—the sysout may show success, but Control-M marks the job Not OK because the expected condition wasn't set. The fix is to trace the condition chain: which upstream job should have set it, did that job run, and did it set the condition with the right date? Use ctmcontb to list active conditions and their timestamps. If the condition is missing, rerunning the downstream job won't help—you need to fix the upstream job first.
Idempotency: The Rerun Safety Check
Before you rerun any job, ask one question: if this job runs twice, will it cause harm? If the answer is yes, you cannot safely rerun without manual cleanup. Idempotent jobs can be rerun freely—they check for existing work and skip or overwrite safely. Non-idempotent jobs (payments, emails, inserts without deduplication) require a pre-rerun check: query the target system for evidence of the first run. If the job wrote data, you must either roll back or make the job idempotent before rerunning. The safest pattern is to build idempotency into every job from day one: use unique keys, check for existing records, and make writes upserts.
The Safe Rerun Procedure: Step by Step
Once you've diagnosed the failure and confirmed idempotency, rerun in a controlled way. First, clear the failure condition if one exists (e.g., delete the 'JOB_FAILED' condition). Second, use 'Rerun' from the job's context menu. Third, monitor the rerun's sysout in real time. Fourth, if the job has downstream dependencies, verify they trigger correctly after the rerun. Never rerun a job that's part of a critical path without notifying the on-call team—they may be investigating the same failure.
When the Failure Is in the Script, Not the Scheduler
Sometimes the job ends Not OK because the script itself has a bug—a missing file, a bad variable, or a command that returns non-zero on success (like grep with no matches). These are the easiest to fix but the hardest to spot because the Control-M log only shows the exit code. The fix is to make your scripts exit 0 on success and non-zero only on real failures. Use set -e carefully—it can cause false failures on commands like grep that return 1 for 'no match'. Always test your script outside Control-M first, then wrap it in a job that captures the exit code explicitly.
|| true after commands that return non-zero on benign conditions, or explicitly check $? and exit 0. Never let a trailing grep determine your job's success.Rerunning with Dependencies: Don't Break the Chain
If the failed job has downstream dependents, rerunning it may trigger them prematurely or leave them waiting. Control-M handles this via conditions and the job's 'Rerun' behavior. When you rerun a job, it will set its out-conditions again, which may trigger downstream jobs that already ran. To avoid duplicate downstream runs, either set the downstream jobs to 'Wait for condition' with a date check, or use Control-M's 'Rerun' option that does not re-trigger dependents. The safest approach: rerun the failed job, then manually verify each downstream job's status and rerun only those that actually need it.
When Not to Rerun: The Manual Intervention Cases
Some failures should never be rerun automatically. If the job failed due to data corruption, a schema change, or a business rule violation, rerunning will just fail again or cause more damage. Examples: a job that loads data into a table with a unique constraint violation, a job that sends customer emails, or a job that calls a third-party API with rate limits. In these cases, you must fix the root cause first—clean the data, update the schema, or wait for the rate limit to reset—then rerun. The rule: if the failure is deterministic (same input, same error), rerunning is pointless. Fix the input or the code first.
The Rerun That Doubled the Payroll
- Never rerun a job until you've read its sysout and confirmed it is idempotent—or you've verified it failed before any side effect.
|| true or explicit exit 0). 3. Rerun the job.In Control-M GUI: right-click job → View Outputgrep -i 'error\|exception\|failed' /path/to/sysout.log| File | Command / Code | Purpose |
|---|---|---|
| inspect_job_failure.sh | JOB_LOG="/tmp/controlm_job_$$.log" | Why 'Ended Not OK' Is a Symptom, Not a Diagnosis |
| sysout_vs_ctlmlog.sh | cat > /tmp/db_job.sh <<'EOF' | Reading the Sysout |
| condition_trace.sh | echo "Condition: DAILY_SALES_LOADED" | Conditions |
| idempotent_payment.py | from datetime import date | Idempotency |
| safe_rerun_checklist.sh | JOB_NAME="PAYROLL_TRANSFER" | The Safe Rerun Procedure |
| script_exit_code_fix.sh | LOG_FILE="/tmp/app.log" | When the Failure Is in the Script, Not the Scheduler |
| dependency_rerun.sh | FAILED_JOB="SALES_LOAD" | Rerunning with Dependencies |
| deterministic_failure.sh | ERROR_MSG="ORA-00001: unique constraint (HR.EMP_EMAIL_UK) violated" | When Not to Rerun |
Key takeaways
Interview Questions on This Topic
How does Control-M determine that a job ended Not OK, and where does the real failure information live?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.
That's Control-M. Mark it forged?
3 min read · try the examples if you haven't