Troubleshooting · Developer docs

Troubleshooting

Grouped by symptom, never by module, because the person with the problem does not know which module has it. One hundred questions across ten pages, each answered with the causes in the order they usually turn out to be true.

Symptom 10 questions

Jobs and automation

Jobs were called hooks until the rename; the admin lists them under Customization › Orchestration. Six job symptoms, from a job that never fired to one that works on staging and not on production, each with its causes in the order they usually turn out to be true, and four answers about the rename.

Nearly every job problem is answered by the run's log, or by its absence. So the first check for all six is the same: open Customization › Orchestration › Batch Logs, filter to the job, and see whether a batch exists for the moment in question. No batch means the job never started; a batch means it started, and its actions say what happened.

Older pages may still say hook. It is the same thing: the rename lists each old name next to its new one.

I cannot find Hooks in the admin

Hooks were renamed. They are under Customization › Orchestration now, and a hook is called a job. Only the names changed: every hook a site had is one of its jobs, with the same triggers, conditions, actions and settings.

  • Jobs lists what used to be hooks; Add Job creates one.
  • Batch Logs records every run of every job, as before.
  • Categories and Configuration are where they were.

Older pages still say hook: read it as job. Are hooks and jobs the same thing? lists each old name next to its new one.

Open this answer on its own page →
My job did not fire

There is no batch at all. The causes, in the order they usually turn out to be true:

  1. It is not published, or was imported and never published: an imported job arrives inert.
  2. It is limited to another site, or its trigger is narrowed to a record id or parent id that is not the record you changed. After an import, ids point at whatever has that id on the new site.
  3. The change was made by an asynchronous job. Records written by a background batch fire no entity events, so a record created or updated by another job does not start this one. Chain the two with Trigger job.
  4. The method is not the one that happened. Publishing is an UpdateRecord, not a create; a form submission's Completed fires once, not on later edits; StartEditingRecord fires only where logging it is switched on.
  5. A web event's URL is wrong, or the job is unpublished: /cdna-api/webhook/<slug> answers 404 for both.

A batch that exists but ended skipped did fire: a condition or a Stop ended it on purpose, and the first condition's result in the log says which.

Open this answer on its own page →
My job fired twice

Two batches for one change. Compare their events:

  • Two methods on one trigger, such as CreateRecord and UpdateRecord, where saving a new record fires both. Guard with a condition on the change you care about.
  • Two saves. Every save is an UpdateRecord, and an editor who saves, then publishes, has saved twice. A job that should act once needs a Pre-Event property and a condition that it changed (how).
  • Two jobs, often a copy left published after a test, or the same job imported twice under different names.
  • A re-run from Batch Logs, or a partner sending the same webhook again.
  • A schedule overlapping itself: the next run is queued whether or not the last one finished.

Whatever the cause, the lasting fix is making the job safe to run twice, with an external id and a lookup before any write (idempotency).

Open this answer on its own page →
A job run stopped halfway through

The batch is error, or still waiting.

  • error: the first action with an error has the message; everything before it ran and was not undone, everything after it did not run. Debug a batch that failed halfway walks through it, including the common messages.
  • waiting for hours: a parallel loop is still processing, or stuck. A batch waits for its loop for eight hours at most, then never reaches the actions after it. Look at the loop elements' statuses; if most are done and a few are not, the other system is slow or rate-limiting. Make the chunks smaller (how).
  • An endpoint returned 500: a synchronous web-event job that fails, or that stops with status skipped or error before replying, answers its caller with 500.

Before re-running it, check that it is safe to: a re-run starts again from the first action.

Open this answer on its own page →
A job is triggering itself in a loop

Batches keep appearing for the same record. Unpublish the job first, then look for the write that feeds it:

  1. A synchronous job that updates the record it watches. Synchronous writes fire entity events; asynchronous ones do not. Make it asynchronous unless it answers a web request.
  2. A job that writes through the site's own API with an HTTP request. That is a web request, so its write fires events whatever the job's mode.
  3. Two jobs that trigger each other, directly with Trigger job or through records each writes.
  4. A two-way sync with another system that sends every change back: the site pushes, the other system saves and echoes, the site saves and pushes again.

The guard is a condition on Pre-Event data that carries on only when the field it reacts to changed, and a write that happens only when it would change something. For syncs, decide which system owns each field and send only those (field ownership).

Open this answer on its own page →
A scheduled job stopped running and nobody noticed

There are no batches since some date. In order:

  1. It lost its schedule or its site: an import does not carry either, and a schedule needs a site. Check both on the job.
  2. It is unpublished, or its schedule's end date has passed.
  3. It runs and fails, and the failures are not kept: batch persistence never or success leaves no trace of an error. Set always or errors.
  4. It ran and did nothing: the batches are there and succeed, but select no records, because a watermark is ahead of the data. That one does not look like a stopped job at all; see how watermarks interact with timezones.
  5. A run was missed: a schedule is due only in its exact minute, and a minute the scheduler missed is not caught up.

Nobody noticed because nothing is watching. Give every job a client relies on an outbound heartbeat or an inbound health check, watched by something outside the platform (monitoring).

Open this answer on its own page →
A job works on staging but not on production

The job is the same; its surroundings are not.

  • Site data is missing or different. It does not travel with an export: keys, URLs and recipient addresses must be set on each environment.
  • Record ids differ. A trigger or action aimed at id 12 on staging is aimed at a different record, or none, on production.
  • The schedule, site or published state did not travel.
  • The template files are not deployed. A Send mail or Render template that names a file under templates/hooks/ fails until the repository is deployed.
  • Email looked fine on staging because it was redirected. A non-production environment sends mail to the development address; on production it goes to the real recipients, and problems with addresses or deliverability appear there first.
  • The other system's production side behaves differently: stricter validation, rate limits, a different key. The HTTP request's response in the batch log says which.
  • The timezone differs, when staging belongs to a client whose timezone setting is not production's (often UTC against the client's own zone). Anything that writes or compares dates as text is off by the difference.

The move itself, with the checks in order: Move jobs from staging to production.

Open this answer on its own page →
The docs say hook and the admin says job: are they the same thing?

Yes. The Hooks module was renamed Orchestration, and a hook became a job. The two words mean the same thing everywhere, so anything written about hooks applies to jobs unchanged.

Old nameNew name
Hooks, the moduleOrchestration
hookjob
hook batchjob run, or job batch; listed under Batch Logs
hook categoryjob category
Trigger hook, the actionTrigger job
synchronous hooksynchronous job

Some names did not change. The admin still calls a run a batch (Batch Logs, Batch persistence), and the API and the code still say hook; why the API still says hooks explains where.

Open this answer on its own page →
What is a job run, and where do I find its log?

A run is one execution of a job, from its trigger to its last action. Each time a job starts, the platform records the run, and the admin lists it as a batch under Customization › Orchestration › Batch Logs. Job run, job batch and batch are the same thing; older pages say hook batch.

  1. Its status says how it ended: success, error, or skipped when a condition or a Stop ended it on purpose. While it is still going it shows initialized, running or waiting.
  2. Its actions are listed one row each, in the order they ran, with what each one output. A loop adds a row for every element.
  3. Whether it is kept depends on the job's Batch persistence. With never or success, a failed run leaves no log at all.

Reading a failed run in the right order is in Debug a job run that failed halfway.

Open this answer on its own page →
Why do the API, the code and my templates still say hooks?

Because only the names people see changed. The module underneath is still called Hooks, so everything a program addresses keeps the old word, and keeps working:

  • API routes. Jobs are managed at /cdna-api/hooks/hook/{id} and listed at /cdna-api/hooks/hooks. Their runs are read at /cdna-api/hooks/batches/{job id} and /cdna-api/hooks/batch/{id}, and a job is started with POST /cdna-api/hooks/hook/{id}/run. A web event answers at /cdna-api/webhook/{slug}.
  • Entity names. A job is Hooks\Entities\Hook and a run is Hooks\Entities\Batch. That is how they appear in the generated Reference and in another job's entity events.
  • Templates. Sites keep job templates (email bodies, request payloads) under templates/hooks/ by convention. Nothing requires the name, and nothing needs moving.

The MCP server uses the new names: coredna_list_jobs, coredna_list_job_runs, coredna_run_job. In code and integrations, keep the routes as they are: they are the only ones the API answers to.

Open this answer on its own page →
Go deeper

Was this page helpful?

Your answer goes to the docs team.