Skip to content

fix(automations): stop inbound runs being dropped and mislogged - #444

Merged
ArnasDon merged 1 commit into
mainfrom
fix/409-automation-dispatch-reliability
Jul 29, 2026
Merged

fix(automations): stop inbound runs being dropped and mislogged#444
ArnasDon merged 1 commit into
mainfrom
fix/409-automation-dispatch-reliability

Conversation

@ArnasDon

Copy link
Copy Markdown
Owner

Summary

Fixes the two defects behind #409's main symptom — automation logs reading status: 'success' with steps_executed: [] ("No steps recorded"). Both are confirmed against main.

What changed

1. src/app/api/whatsapp/webhook/route.ts — await the dispatch loop.

runAutomationsForTrigger was called fire-and-forget inside the route's after() block. after() only keeps the function alive for promises it can see, so the run could be frozen after the log row was inserted but before any step ran. This is issue #301's failure mode recurring one level down — and the file already documents the invariant: the AI auto-reply dispatch 10 lines below is awaited "(same reason as the webhook dispatch below)", and dispatchWebhookEvent below that carries "Awaited — not fire-and-forget — because we're inside the route's after() block". The automations loop was the one outlier.

The per-iteration .catch is kept, so one trigger type failing can't skip the rest of the loop.

2. src/lib/automations/engine.ts — seed the log status pessimistically.

executeAutomation inserted the log row with status: 'success' before executing anything. A run that died mid-flight therefore left a permanent success with an empty step list — indistinguishable from an automation that genuinely had nothing to do, which is exactly what the issue describes. Seeded as 'failed' instead: the status only becomes success if execution reaches a terminal path.

No migration needed — 'failed' is already in the column's CHECK (status IN ('success','partial','failed')), and every terminal path (appendResults at the outermost scope, finalizeLog) overwrites the seed. The wait-step park path still writes 'partial' as before.

Deliberately not in this PR

Two further points from the issue change existing behaviour and want your call rather than mine:

  • Word-boundary keyword matching. triggerMatches does use raw haystack.includes(k) for contains (engine.ts:659), so short keywords do match inside unrelated words. But switching to \b alters which automations fire for anyone already relying on substring matches — a behaviour change, not a bug fix.
  • Re-opening a closed conversation on an inbound reply. Confirmed: the inbound conversation update never touches status. Whether a customer reply should reopen a thread an agent deliberately closed is a product decision. Note the issue also claims this blocks automations — it doesn't; dispatch is keyed on the contact, not the conversation status. The real effect is inbox visibility.

Test plan

  • npm run typecheck clean.
  • npm run lint — 39 warnings, identical to main.
  • npm test — 647 tests pass (2 new).
  • npm run build succeeds.
  • Two regression tests in engine.test.ts: the log row is inserted as failed with no steps, and a completed run is still promoted to success. Mutation-checked — restoring the 'success' seed fails the first and only the first.

The after() change isn't unit-tested; there's no route-level harness on main and the behaviour is a runtime lifetime property rather than something the module boundary exposes.

Related

Refs #409 (partial — see "deliberately not in this PR").

Two defects that together produce the reported symptom — automation logs
that read `status: 'success'` with `steps_executed: []`.

1. The dispatch loop in the inbound webhook was fire-and-forget inside the
   route's `after()` block. `after()` only keeps the function alive for
   promises it can see, so the run could be frozen after the log row was
   inserted but before any step executed. This is the same failure mode as
   issue #301, recurring one level down — the comments on the AI auto-reply
   and `message.received` dispatches immediately below already spell out
   the invariant this violated. Now awaited; the per-iteration `.catch` is
   kept so one trigger type can't skip the rest of the loop.

2. `executeAutomation` seeded the log row with `status: 'success'` before
   running anything. A run that died mid-flight therefore left a permanent
   success with no steps, indistinguishable from an automation that
   genuinely had nothing to do. Seeded as `'failed'` instead, so the status
   only becomes success if execution actually reaches a terminal path. No
   migration needed — 'failed' is already in the column's CHECK constraint,
   and every terminal path overwrites it.

Two more points from the issue are deliberately not addressed here, as
both change existing behaviour and want a maintainer decision: switching
`contains` keyword matching to word boundaries (would alter which
automations fire for current users) and re-opening a closed conversation
on an inbound reply.

Refs #409.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@supabase

supabase Bot commented Jul 28, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project crxylxakpapcfziradbb because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@ArnasDon
ArnasDon merged commit fc1f19b into main Jul 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant