fix(platform): retire automation triggers of a deleted organization - #3025
Open
larryro wants to merge 1 commit into
Open
fix(platform): retire automation triggers of a deleted organization#3025larryro wants to merge 1 commit into
larryro wants to merge 1 commit into
Conversation
Deleting an organization cascaded the audit log, personalization data, config directory and memberMirror — but never its automationTriggers rows. A surviving enabled schedule keeps coming due on the minutely scan, and each run crashes in the agent host's throwing orgSlugFromId: one uncaught OrgSlugUnresolvableError per occurrence, forever, with no returning visitor required. Two layers: the /organization/delete after-hook now deletes the org's trigger rows next to the member-mirror cascade (only after Better Auth confirmed the deletion), and the schedule scan retires any due trigger whose organization no longer resolves — the backstop for orgs deleted before the cascade existed or a hook that failed mid-flight.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deleting an organization cascades a lot — audit log, personalization hard-delete, the org's config directory,
memberMirror— butautomationTriggersrows were never disabled or deleted. A survivingenabled: trueschedule keeps coming due on the minutely scan (crons.ts→scanScheduledTriggers), and each started run crashes deep in the agent host's throwing slug resolver (agent_host.tsorgSlugFromId): one uncaughtOrgSlugUnresolvableErrorper scheduled occurrence, forever, plus the wasted runs. Unlike the #3019 incident this needs no returning visitor — it self-sustains. The incident's org happened to have no schedule triggers, which is the only reason this gap is still latent.Fixes #3022.
What changed
Two layers, so a missed cascade can't error forever:
convex/auth.ts+convex/automations/triggers.ts): the/organization/deleteafter-hook now calls the newinternal.automations.triggers.cascadeDeleteOrgTriggersright next tocascadeDeleteOrgMembersMirror— i.e. only after Better Auth actually deleted the org row, so a failed deletion keeps a live org's schedules and webhook tokens intact. Deleting rather than disabling matches the platform's hard-delete posture for org-owned rows (memberMirror, personalization); the mutation is idempotent and indexed (by_org). It has its own catch + warn so a failure is neither mislabeled as a mirror failure nor fatal to the hook.scanScheduledTriggers): a due trigger now resolves its org viaorgSlugFromIdOrNull(the same graceful pattern the crawler uses inknowledge/crawl_action.ts); if the org no longer resolves, the trigger is patchedenabled: falsewith a warn and never starts a run. This retires the orphans that pre-date the cascade, and anything a failed hook leaves behind. The lookup runs only for due triggers (not per scan row), so the steady-state cost of the minutely scan is unchanged; a transient lookup failure still throws, and the scan simply retries next tick.Webhook and event paths need no scan-side guard: their trigger rows die with the cascade, and a deleted row already yields the token-secrecy 404.
Tests
convex/automations/triggers.test.ts:enabled: false, no run row exists, and the next scan examines nothing;components.betterAuth.adapter.create, thearena_action.test.tspattern) — required because a schedule that should fire now needs an org that resolves; the fixture org names deliberately don't.Full
convex/automations/suite: 236 passed (14 files);convex/auth.test.ts7 passed.Not in scope (follow-up candidates)
Other org-scoped tables (
automationVersions,automationRuns,websites, …) still keep orphan rows after an org deletion — inert without a trigger, but worth an orphan-row policy audit as noted in #3022.Related: #3019 (the incident this was split from), sibling PRs for #3019/#3020/#3021.
Gate:
tsc --noEmitclean,oxlint --type-awareclean, oxfmt, vitest 243 passed (automations + auth).