Skip to content

event-dispatch job resolves zero targets — no sink runs (main only, since #384) #431

Description

@CraigSteven

On main, every event resolves to zero targets, so nothing runs: integrations, webhooks, notifications, ai, summary, workflow, app-webhook, remote-status-push. The job logs outcome: succeeded and enqueues no hook jobs. Silent — no warning, no error, no retry.

runEventDispatch reads the registry but nothing populates it:

  • events/event-dispatch-queue.ts:20import { resolveTargets } from './resolvers/registry'
  • events/resolvers/index.ts:20registerAllResolvers() is the only caller of registerResolver
  • its only call site is events/targets.ts:188, inside the legacy getHookTargets() adapter, which this path does not use
  • jobs/definitions.ts:290 calls runEventDispatch(job) with no deps, so deps.resolve ?? resolveTargets takes the bare import
  • resolvers/registry.ts imports only logger and types, so nothing registers as a side effect

This is the only dispatch path: emit() writes dispatch_owner: 'job' (events/emit.ts:64) and convertRelayOwnedEvents rewrites leftover relay rows to job.

Introduced by #384 (2026-08-21), which added event-dispatch-queue.ts with this import and in the same change deleted "the legacy direct getHookTargets + bulk-add path" — i.e. it replaced the one caller that registered the resolvers with one that does not. Not in any release: main and the current feature branches only.

Registration currently lives incidentally inside getHookTargets(), so any path that bypasses that adapter loses every sink silently. Doing it at worker start, or inside resolveTargets itself, would make that structurally impossible.

Environment

ghcr.io/quackbackio/quackback:main, self-hosted via docker-compose.prod.yml on Ubuntu, single workspace, Postgres 17, Redis 7. Behaviour is server-side, so browser is not a factor.

Reproduce

Enable a Jira post.created mapping (or subscribe a second user to a post so a notification is expected), then create a post:

{"component":"dispatch","event_type":"post.created","msg":"dispatching event"}
{"queue":"event-dispatch","event":"job.started"}
{"queue":"event-dispatch","event":"job.finished","duration_ms":30,"outcome":"succeeded"}

Actual: no hook job follows, no resolver logs anything, and the event is marked published.

Expected: resolveTargets returns the targets of every registered sink interested in post.created, and hook jobs are enqueued for them — as happens on v0.13.x, where the same actions produced hook attempts and notification emails.

Not covered by tests

Every call in __tests__/event-dispatch-queue.test.ts injects a stub:

await runEventDispatch(job(eventId), { resolve: webhookAndWorkflow, enqueue })

so the ?? resolveTargets fallback — the only branch production takes — is untested.

Branches checked

Present and identical on main (0204d1e), fix/messenger-defaults-followup (ca80608), feat/simpler-messenger-defaults (7476908) and saas (003b06a).

Fix

import { registerAllResolvers, resolveTargets } from './resolvers'
registerAllResolvers() // idempotent

or import the barrel for its side effect, as its docblock describes.

Suggested regression test

The gap is that no test omits resolve. One that does would have caught this:

it('resolves through the real registry when no resolver is injected', async () => {
  // registry is module state — isolate so another test file's barrel import
  // cannot pre-populate it and mask the bug
  vi.resetModules()
  const { runEventDispatch } = await import('../event-dispatch-queue')

  const enqueue = vi.fn()
  await runEventDispatch(job(eventId), { enqueue }) // no `resolve`

  expect(enqueue).toHaveBeenCalled()
})

Worth pairing with a guard so a future regression of this shape isn't silent — resolveTargets logging a warn (or throwing) when resolvers is empty, since "no sinks registered" and "sinks found nothing" are currently indistinguishable.

Yes, Claude wrote this for me!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions