The most expensive Airtable problems we get called into are rarely dramatic. Nothing is broken on screen. The base looks fine. But an automation stopped firing eleven days ago, so 300 invoices never got their reminder email, and nobody noticed because a stopped automation looks exactly like a quiet week.
Airtable's automations are reliable at running. They are weak at telling you when they did not run, or ran and did the wrong thing. This tutorial closes that gap: how to read what Airtable already records, how to make failures loud, and how to build automations that survive being rerun.
The four ways an automation goes quiet
Before instrumenting anything, know what you are watching for.
- The automation errored. A step failed: a script threw, an API call returned 500, a field it referenced was renamed or deleted. Airtable logs the failure in run history and, after repeated consecutive failures, will turn the automation off.
- The automation was paused. Someone toggled it off while testing, or Airtable disabled it after a run of errors. It is now sitting there, grey, doing nothing.
- The trigger never fired. The most insidious one. "When record enters view" only fires if the record actually enters the view; a filter change, a broken formula, or an upstream automation that stopped means no record ever qualifies. No error, no run, no evidence.
- It ran and silently did nothing useful. A conditional step short-circuited, a find-records step returned zero results, or a script caught its own exception and swallowed it. Run history shows a green tick. Nothing happened.
Run history catches 1 and 2. You have to build detection for 3 and 4 yourself.
Step 1: actually read run history
Open the Automations panel, select an automation, and open its run history. Each entry shows the trigger record, the input and output of every step, and the failure message if there was one.
Things worth knowing before you rely on it:
- History is retained for a limited window (weeks, not forever, and shorter on lower plans). It is a debugging tool, not an audit log. If you need a permanent record, write your own — step 3 below.
- Runs are listed per automation. There is no cross-base "show me everything that failed today" view. On Enterprise plans the admin panel gives more visibility, but most teams do not have that.
- The output panel of each step is the fastest debugging tool in Airtable. When a script misbehaves, look at what the previous step actually handed it — nine times out of ten the input is a different shape than you assumed (an array where you expected a string, an empty array where you expected one record).
Make reading run history a weekly ritual for critical automations until your monitoring is in place. Ten minutes on Monday finds most of what would otherwise take a month to surface.
Step 2: make each step fail loudly
By default, an Airtable automation stops at the first failing step and marks the run as failed. That is the behaviour you want — as long as somebody sees it. What you do not want is code that hides the problem.
Do not swallow errors in scripts
This is the anti-pattern we find most often in inherited bases:
try {
await fetch(endpoint, options);
} catch (e) {
// keep going
}
The run is green. The API call never landed. Instead, catch, record, then rethrow so the run is marked failed:
let table = base.getTable('Automation log');
try {
let res = await fetch(endpoint, options);
if (!res.ok) {
throw new Error(`Endpoint returned ${res.status}: ${await res.text()}`);
}
} catch (e) {
await table.createRecordAsync({
'Automation': 'Invoice sync',
'Status': { name: 'Error' },
'Detail': String(e).slice(0, 900),
});
throw e; // let the run fail so Airtable records it too
}
Fail fast on bad inputs
Scripts receive trigger data through input.config(). Validate it before doing work, so the error message names the problem instead of surfacing as Cannot read properties of undefined:
let { recordId, email } = input.config();
if (!recordId) throw new Error('No recordId in input config — check the step mapping.');
if (!email || !email.includes('@')) throw new Error(`Invalid email for record ${recordId}: ${email}`);
Retry transient failures, not logical ones
A 429 or a 503 from a third-party API deserves a retry. A 400 does not — retrying a malformed request just burns runs. A small backoff helper inside a Run script step:
async function fetchWithRetry(url, options, attempts = 3) {
for (let i = 0; i < attempts; i++) {
let res = await fetch(url, options);
if (res.ok) return res;
if (res.status < 500 && res.status !== 429) {
throw new Error(`Permanent failure ${res.status}: ${await res.text()}`);
}
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
throw new Error('Exhausted retries');
}
Keep total script runtime within the automation script time limit; three attempts with a 1s/2s/4s backoff is a sensible ceiling. Anything longer belongs in a queue pattern (step 6).
Step 3: build a run-log table
Run history expires and cannot be filtered across automations. A run-log table fixes both problems and takes ten minutes.
Create a table Automation log with:
Name— single line text (the automation name).Status— single select:Success,Error,Skipped.Run at— created time.Record link— single line text or a link to the affected record.Detail— long text (error message, or a one-line summary of what was done).Duration ms— number, optional but useful for spotting creeping slowness.
Then add a final Run script step to each important automation that writes one row:
let log = base.getTable('Automation log');
let { recordId, summary } = input.config();
await log.createRecordAsync({
'Name': 'Invoice reminder',
'Status': { name: 'Success' },
'Record link': recordId,
'Detail': summary || 'Completed',
});
Now one grouped view — group by Name, filter Run at is within the last 7 days — tells you at a glance which automations are alive, which are erroring, and which have gone silent. Put that view on an interface page and it becomes an ops dashboard anybody can read.
Keep the log tidy: a weekly scheduled automation that deletes Success rows older than 30 days stops the table becoming the biggest thing in your base.
Step 4: alert on failure and on silence
Alert on failure
Add an automation triggered by When record created in Automation log, with a condition Status is Error, sending a Slack message or email to whoever owns the base. Include the automation name and the Detail field in the message body. One rule, covers every instrumented automation at once.
Alert on silence (the heartbeat)
This is the one that catches problem type 3, the trigger that never fires. Build a scheduled automation, daily at 08:00:
- Trigger: at scheduled time.
- Find records in
Automation logwhereNameisInvoice reminderandRun atis within the past 1 day. - Conditional: if the find-records step returned 0 records, send an alert: "Invoice reminder has not run in 24 hours."
Use the find-records output length in the condition rather than a script if you can — fewer moving parts. For several automations at once, a small script is easier:
let log = base.getTable('Automation log');
let expected = { 'Invoice reminder': 24, 'CRM sync': 2, 'Nightly export': 26 }; // hours
let since = new Date(Date.now() - 48 * 3600 * 1000);
let rows = await log.selectRecordsAsync({ fields: ['Name', 'Run at'] });
let latest = {};
for (let r of rows.records) {
let name = r.getCellValueAsString('Name');
let at = new Date(r.getCellValue('Run at'));
if (at > since && (!latest[name] || at > latest[name])) latest[name] = at;
}
let stale = Object.entries(expected)
.filter(([name, hours]) => !latest[name] || (Date.now() - latest[name]) > hours * 3600 * 1000)
.map(([name]) => name);
output.set('stale', stale.join(', '));
output.set('hasStale', stale.length > 0);
Follow it with a conditional step on hasStale that emails the list. Heartbeat monitoring is what separates an automation you can trust from one you merely hope about.
Step 5: recover a paused automation properly
When Airtable turns an automation off after repeated errors, the reflex is to switch it back on. Do that and every record that qualified while it was off is still sitting there unhandled — or, worse, the trigger fires for all of them at once.
A safer recovery sequence:
- Read the last failed run in history and fix the actual cause before re-enabling anything.
- Work out the backlog: which records should have been processed during the outage? A view filtered on
Created timeis after the outage start, and the "processed" checkbox is unchecked, usually answers it. - Decide whether the backlog should be processed at all. Eleven days of late reminder emails sent in one burst is often worse than sending none.
- Re-enable, then process the backlog deliberately — a manual button on an interface, or by re-entering records into the trigger view in controlled batches.
This is why "when record enters view" beats "when record created" for anything important: the view is a queue you can requeue from. A created-time trigger gives you exactly one chance forever.
Step 6: design so a rerun is harmless
Monitoring is only half of it. If you must be able to rerun safely, build for idempotency from the start.
- Mark completion in the record. A
Processed atdate or aSync statussingle select, written by the last step of the automation. The trigger view excludes anything already marked, so a rerun skips it. - Send an external idempotency key where the receiving system supports it (Stripe, many webhook endpoints). Use the Airtable record ID plus the operation name.
- Split the trigger from the work. A trigger writes a row to a
Jobstable; a scheduled automation drains the queue every 15 minutes and marks each job done. Failures leave the job un-drained rather than lost, and retries are free. - Never make an automation's output the input to itself without a guard field, or a rerun becomes an infinite loop that eats your automation run allowance in an afternoon.
A 20-minute checklist for an existing base
Go through this on any base you inherited:
- List every automation and mark the three that would hurt most if they stopped.
- Open run history on each; note the last successful run and any error pattern.
- Check for automations currently toggled off that nobody meant to turn off.
- Create the
Automation logtable and add a logging step to those three. - Add the failure alert automation.
- Add a daily heartbeat for the one that runs on a schedule.
- Add a
Processed atfield to any automation whose rerun would double-send something.
That is one afternoon, and it converts "we think it is working" into "we would know within a day if it were not".
Where this stops being enough
If you are running dozens of interlocking automations, hitting monthly run limits, or coordinating work across several bases and external systems, native monitoring starts to creak. At that point the answer is usually an external orchestrator (n8n, Make, or your own service) driving Airtable through the API, with Airtable as the system of record and the interface layer rather than the scheduler.
If an automation in your base has been quietly failing and you are unpicking the damage, that is exactly the kind of work our Airtable project rescue team does. For ongoing instrumentation, monitoring and maintenance, see Airtable training and support, or get in touch with a short description of what stopped working and when you noticed.