Skip to content

feat(error-logs): record the channel-side contact id and stack trace on provider failures - #1139

Merged
hoangle398 merged 2 commits into
mainfrom
fix/flow-error-logs
Sep 12, 2026
Merged

hoangle398 merged 2 commits into
mainfrom
fix/flow-error-logs

Conversation

@hoangle398

@hoangle398 hoangle398 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
image Two changes to what an `ErrorLog` row tells you about a provider failure: **who** it concerned, and **where in our code** it surfaced.

sourceId — who

Adds ErrorLog.sourceId — the contact's channel-side identity (Messenger PSID, IGSID, WhatsApp wa_id) — so a provider failure is attributable even when contactId is null, which is exactly the case that matters most: the contact row does not exist yet (a creation-path getProfile failure, a Lead Ads lead).

  • new nullable sourceId column plus migration; no FK, since ContactInbox.sourceId is unique only per inboxId and the row must outlive the contact inbox entirely
  • threads the id through eventContextSchema and the error-log event payload, and onto the logProviderError call sites that have a contact in scope
  • withholds it from the analytics-scoped public API: the same identity is public only under the contacts scope, so the response is an explicit allow-list and the list's keyword search skips the column
  • reformats the Graph error code prefix to (#10 - …), matching Meta's own shape so re-formatting is idempotent

stackTrace — where

ErrorLog said what failed and who it concerned, but not where. With ~41 call sites — and five Google Sheets operations sharing one helper — a row was often ambiguous about which one produced it.

  • new developer-only ErrorLog.stackTrace column plus migration, capped and stripped of the message prefix by resolveStackFrames (@chatbotx.io/utils/error-log)
  • withheld from every read surface. ErrorLog is workspace-facing (the builder table plus the workspace-token API) and a stack leaks absolute server paths and our internal call chain. WITHHELD_ERROR_LOG_COLUMNS declares the policy once; the DB projection, both response schemas and the UI resource type all derive from it, so the next developer-only column is withheld from all of them by being named there
  • sorting is now allow-listed (SORTABLE_COLUMNS) rather than gated on "is a real column on the model": ordering by a column the caller cannot read is a lexicographic oracle over it, so the allow-list keeps the next withheld column safe on the day it lands
  • call sites hand the service the raw throw instead of a normalized copy — normalizeError returns a plain object, so error instanceof Error failed and every one of those rows stored a NULL stack. resolveMessage gained a readable fallback so a message-less object throw no longer writes [object Object] into the workspace-facing detail
  • the terminal outbound-send path parses the throw into a ParsedError before it reaches the Redis stream, so it captures frames at the emit site and forwards them explicitly — otherwise the densest path in the system could never derive a stack
  • a dozen flow-step catch blocks collapse into one logStepProviderError helper

Verification

pnpm --filter worker test (2227), pnpm --filter builder test (3466), pnpm --filter @chatbotx.io/business test (2308), check-types on builder + business, and the @chatbotx.io/database drift check — all clean.

The migration is generated but not applied — it needs the usual approval before db:migrate runs anywhere.

@github-actions github-actions Bot added the feature New feature or request label Sep 10, 2026
@hoangle398
hoangle398 force-pushed the fix/flow-error-logs branch 2 times, most recently from a914e13 to 138212e Compare September 10, 2026 16:10
@hoangle398 hoangle398 changed the title feat(error-logs): record the channel-side contact id on provider failures feat(error-logs): record the channel-side contact id and stack trace on provider failures Sep 12, 2026
…ures

Adds `ErrorLog.sourceId` — the contact's channel-side identity (Messenger
PSID, IGSID, WhatsApp `wa_id`) — so a provider failure is attributable even
when `contactId` is null, which is exactly the case that matters most: the
contact row does not exist yet (a creation-path `getProfile` failure, a Lead
Ads lead).

- new nullable `sourceId` column plus migration; no FK, since
  `ContactInbox.sourceId` is unique only per `inboxId` and the row must
  outlive the contact inbox entirely
- threads the id through `eventContextSchema` and the error-log event payload,
  and onto the `logProviderError` call sites that have a contact in scope
- withholds it from the `analytics`-scoped public API: the same identity is
  public only under the `contacts` scope, so the response is an explicit
  allow-list and the list's keyword search skips the column
- reformats the Graph error code prefix to `(#10 - …)`, matching Meta's own
  shape so re-formatting is idempotent
`ErrorLog` said what failed and who it concerned, but not where in our code
the failure surfaced — with ~41 call sites, and five Google Sheets operations
sharing one helper, a row was often ambiguous.

The stack is stored in a new developer-only `ErrorLog.stackTrace` column and
withheld from every read surface: `ErrorLog` is workspace-facing (the builder
table plus the workspace-token API) and a stack leaks absolute server paths
and our internal call chain. `WITHHELD_ERROR_LOG_COLUMNS` declares the policy
once and the DB projection, response schemas and UI resource type all derive
from it, so a future developer-only column is withheld from all of them by
being named there.

Sorting is now allow-listed (`SORTABLE_COLUMNS`) rather than gated on "is a
real column": ordering by a column the caller cannot read is a lexicographic
oracle over it, so the allow-list keeps the next withheld column safe on the
day it lands.

Callers now hand the service the raw throw instead of a normalized copy —
`normalizeError` returns a plain object, so `error instanceof Error` failed
and every one of those rows stored a NULL stack.
@hoangle398
hoangle398 merged commit 0ad9c0b into main Sep 12, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant