Skip to content

feat(error_tracking): add native alert, destination, and thread models - #90533

Merged
trunk-io[bot] merged 4 commits into
cat/et-alerts-lifecycle-eventsfrom
cat/et-alerts-models
Sep 2, 2026
Merged

trunk-io[bot] merged 4 commits into
cat/et-alerts-lifecycle-eventsfrom
cat/et-alerts-models

Conversation

@cat-ph

@cat-ph cat-ph commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Problem

An error tracking "alert" today is a HogFunction with special filter strings: there is no entity to hold state, throttling, delivery history, or destinations. The native stateful alerting RFC introduces a first-class alert configuration; this layer adds its storage.

Stacked on #90532. No behavior changes — nothing reads or writes these tables yet.

Changes

  • New ErrorTrackingAlert model: name, enabled, triggers list (thread openers), hog-function-shaped filters with embedded bytecode, per-issue opener throttle_seconds, creator.
  • New ErrorTrackingAlertDestination model: one delivery target per row (channel type via callable choices — only slack exists today — integration FK, config).
  • New ErrorTrackingAlertThread model: runtime state for one (alert, issue, destination) conversation — provider message ref, root headline, delivered notification ids — with a unique constraint as the delivery concurrency primitive.
  • One additive migration creating the three tables; FKs to hot tables (posthog_team, posthog_user) use db_constraint=False so creates stay lock-free.

How did you test this code?

  • sqlmigrate output verified: three CREATE TABLEs plus indexes/constraints, no hot-table ALTERs; makemigrations --check reports no drift.
  • Model behavior (constraints, scoping) is exercised by the API tests in the next layer of the stack.

Automatic notifications

  • Publish to changelog?

Docs update

None — schema only, behind later layers.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Authored with Claude Code. Extended the flat POC model (cat/alerting-v2-poc) into the RFC's three-table shape: destinations split from the alert so filters evaluate once and fan out, and the thread row gained the destination FK plus root headline per RFC review.
  • Skills invoked: /django-migrations, /writing-dataclasses, /writing-pr-descriptions, /stacking-prs.

@github-actions

github-actions Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Backend coverage — 97.0% of changed backend lines covered — 6 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ███████████████████░ 97.0% (259 / 265)

File Patch Uncovered changed lines
products/error_tracking/backend/logic/lifecycle_events.py 90.0% 49–50, 69, 136, 139
products/error_tracking/backend/models.py 98.0% 939

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 33572026404 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
warehouse_sources_queue ██████░░░░░░░░░░░░░░ 29.4% 91 / 309
demo ███████████░░░░░░░░░ 56.9% 1,518 / 2,666
autoresearch ████████████░░░░░░░░ 62.5% 5 / 8
data_tools █████████████░░░░░░░ 66.2% 90 / 136
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
batch_exports ████████████████░░░░ 79.8% 19,679 / 24,669
apm █████████████████░░░ 83.3% 1,234 / 1,481
signals █████████████████░░░ 84.6% 33,335 / 39,413
cdp █████████████████░░░ 85.2% 4,310 / 5,056
wizard █████████████████░░░ 86.7% 1,090 / 1,257
data_modeling █████████████████░░░ 86.7% 9,654 / 11,133
notebooks ██████████████████░░ 87.7% 11,202 / 12,770
product_tours ██████████████████░░ 87.9% 1,303 / 1,482
data_warehouse ██████████████████░░ 88.6% 12,782 / 14,425
engineering_analytics ██████████████████░░ 89.0% 7,707 / 8,656
cohorts ██████████████████░░ 89.1% 7,370 / 8,273
business_knowledge ██████████████████░░ 89.1% 4,440 / 4,984
dashboards ██████████████████░░ 89.6% 6,807 / 7,598
tasks ██████████████████░░ 90.0% 63,212 / 70,240
streamlit_apps ██████████████████░░ 90.2% 2,500 / 2,773
exports ██████████████████░░ 90.5% 8,717 / 9,637
links ██████████████████░░ 90.6% 183 / 202
managed_warehouse ██████████████████░░ 90.7% 8,563 / 9,436
alerts ██████████████████░░ 90.9% 5,182 / 5,700
visual_review ██████████████████░░ 90.9% 6,880 / 7,567
conversations ██████████████████░░ 91.0% 20,910 / 22,973
canvas ██████████████████░░ 91.0% 4,913 / 5,397
mcp_analytics ██████████████████░░ 91.2% 4,374 / 4,795
error_tracking ██████████████████░░ 91.6% 12,905 / 14,090
notifications ██████████████████░░ 92.2% 1,101 / 1,194
early_access_features ███████████████████░ 92.5% 1,323 / 1,430
managed_migrations ███████████████████░ 92.6% 1,556 / 1,681
slack_app ███████████████████░ 92.8% 12,354 / 13,315
stamphog ███████████████████░ 92.9% 5,775 / 6,216
web_analytics ███████████████████░ 93.1% 18,597 / 19,966
context_layer ███████████████████░ 93.3% 2,573 / 2,758
surveys ███████████████████░ 93.4% 6,051 / 6,482
reminders ███████████████████░ 93.4% 468 / 501
mcp_store ███████████████████░ 93.4% 7,609 / 8,144
posthog_ai ███████████████████░ 93.5% 1,372 / 1,468
marketing_analytics ███████████████████░ 93.5% 17,322 / 18,526
ai_observability ███████████████████░ 93.7% 17,988 / 19,194
approvals ███████████████████░ 93.7% 3,492 / 3,725
billing_alerts ███████████████████░ 93.9% 2,078 / 2,212
workflows ███████████████████░ 94.1% 11,042 / 11,739
legal_documents ███████████████████░ 94.1% 1,970 / 2,093
review_hog ███████████████████░ 94.3% 9,982 / 10,583
endpoints ███████████████████░ 94.4% 8,866 / 9,396
tracing ███████████████████░ 94.4% 2,678 / 2,837
access_control ███████████████████░ 94.9% 5,828 / 6,139
customer_analytics ███████████████████░ 95.0% 17,702 / 18,639
annotations ███████████████████░ 95.2% 795 / 835
product_analytics ███████████████████░ 95.3% 19,292 / 20,252
replay_vision ███████████████████░ 95.4% 26,086 / 27,355
experiments ███████████████████░ 95.5% 29,271 / 30,660
actions ███████████████████░ 95.5% 706 / 739
skills ███████████████████░ 95.6% 5,403 / 5,651
messaging ███████████████████░ 95.8% 3,636 / 3,795
logs ███████████████████░ 95.9% 13,495 / 14,077
growth ███████████████████░ 95.9% 7,644 / 7,969
feature_flags ███████████████████░ 96.1% 19,585 / 20,382
revenue_analytics ███████████████████░ 96.3% 1,841 / 1,911
user_interviews ███████████████████░ 96.5% 2,639 / 2,735
warehouse_sources ███████████████████░ 97.2% 406,037 / 417,835
data_quality ███████████████████░ 97.5% 4,103 / 4,209
analytics_platform ████████████████████ 98.1% 2,286 / 2,330
data_catalog ████████████████████ 98.3% 3,234 / 3,289
metrics ████████████████████ 98.3% 3,505 / 3,564
pulse ████████████████████ 98.4% 2,017 / 2,049
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

⚠️ Django migration SQL — 1 new migration to review

We've detected new migrations on this PR. Review the SQL output for each migration:

products/error_tracking/backend/migrations/0031_errortrackingalert_errortrackingalertdestination_and_more.py

BEGIN;
--
-- Create model ErrorTrackingAlert
--
CREATE TABLE "posthog_errortrackingalert" ("id" uuid NOT NULL PRIMARY KEY, "name" text NOT NULL, "enabled" boolean NOT NULL, "triggers" jsonb NOT NULL, "filters" jsonb NOT NULL, "throttle_seconds" integer NOT NULL CHECK ("throttle_seconds" >= 0), "created_at" timestamp with time zone NOT NULL, "updated_at" timestamp with time zone NOT NULL, "created_by_id" integer NULL, "team_id" integer NOT NULL);
--
-- Create model ErrorTrackingAlertDestination
--
CREATE TABLE "posthog_errortrackingalertdestination" ("id" uuid NOT NULL PRIMARY KEY, "channel_type" text NOT NULL, "config" jsonb NOT NULL, "created_at" timestamp with time zone NOT NULL, "updated_at" timestamp with time zone NOT NULL, "alert_id" uuid NOT NULL, "integration_id" integer NULL, "team_id" integer NOT NULL);
--
-- Create model ErrorTrackingAlertThread
--
CREATE TABLE "posthog_errortrackingalertthread" ("id" uuid NOT NULL PRIMARY KEY, "external_ref" jsonb NOT NULL, "root_headline" text NOT NULL, "delivered_notification_ids" jsonb NOT NULL, "created_at" timestamp with time zone NOT NULL, "updated_at" timestamp with time zone NOT NULL, "alert_id" uuid NOT NULL, "destination_id" uuid NOT NULL, "issue_id" uuid NOT NULL, "team_id" integer NOT NULL);
--
-- Create index idx_et_alert_team_enabled on field(s) team, enabled of model errortrackingalert
--
CREATE INDEX "idx_et_alert_team_enabled" ON "posthog_errortrackingalert" ("team_id", "enabled");
--
-- Create constraint unique_error_tracking_alert_thread on model errortrackingalertthread
--
ALTER TABLE "posthog_errortrackingalertthread" ADD CONSTRAINT "unique_error_tracking_alert_thread" UNIQUE ("alert_id", "issue_id", "destination_id");
CREATE INDEX "posthog_errortrackingalert_created_by_id_f9169e7e" ON "posthog_errortrackingalert" ("created_by_id");
CREATE INDEX "posthog_errortrackingalert_team_id_237990b2" ON "posthog_errortrackingalert" ("team_id");
ALTER TABLE "posthog_errortrackingalertdestination" ADD CONSTRAINT "posthog_errortrackin_alert_id_dad2e16d_fk_posthog_e" FOREIGN KEY ("alert_id") REFERENCES "posthog_errortrackingalert" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "posthog_errortrackingalertdestination" ADD CONSTRAINT "posthog_errortrackin_integration_id_bf6616c9_fk_posthog_i" FOREIGN KEY ("integration_id") REFERENCES "posthog_integration" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "posthog_errortrackingalertdestination_alert_id_dad2e16d" ON "posthog_errortrackingalertdestination" ("alert_id");
CREATE INDEX "posthog_errortrackingalertdestination_integration_id_bf6616c9" ON "posthog_errortrackingalertdestination" ("integration_id");
CREATE INDEX "posthog_errortrackingalertdestination_team_id_03f2a5ee" ON "posthog_errortrackingalertdestination" ("team_id");
ALTER TABLE "posthog_errortrackingalertthread" ADD CONSTRAINT "posthog_errortrackin_alert_id_94f1ed46_fk_posthog_e" FOREIGN KEY ("alert_id") REFERENCES "posthog_errortrackingalert" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "posthog_errortrackingalertthread" ADD CONSTRAINT "posthog_errortrackin_destination_id_98175bc9_fk_posthog_e" FOREIGN KEY ("destination_id") REFERENCES "posthog_errortrackingalertdestination" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "posthog_errortrackingalertthread" ADD CONSTRAINT "posthog_errortrackin_issue_id_4e6ea270_fk_posthog_e" FOREIGN KEY ("issue_id") REFERENCES "posthog_errortrackingissue" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "posthog_errortrackingalertthread_alert_id_94f1ed46" ON "posthog_errortrackingalertthread" ("alert_id");
CREATE INDEX "posthog_errortrackingalertthread_destination_id_98175bc9" ON "posthog_errortrackingalertthread" ("destination_id");
CREATE INDEX "posthog_errortrackingalertthread_issue_id_4e6ea270" ON "posthog_errortrackingalertthread" ("issue_id");
CREATE INDEX "posthog_errortrackingalertthread_team_id_dd7e50a8" ON "posthog_errortrackingalertthread" ("team_id");
COMMIT;

Last updated: 2026-09-01 23:42 UTC (7bf3f70)

✅ Django migration risk — migration analysis complete

We've analyzed your migrations for potential risks.

Summary: 1 Safe | 0 Needs Review | 0 Blocked

✅ Safe

Brief or no lock, backwards compatible

error_tracking.0031_errortrackingalert_errortrackingalertdestination_and_more
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: ErrorTrackingAlert
  └─ #2 ✅ CreateModel
     Creating new table is safe
     model: ErrorTrackingAlertDestination
  └─ #3 ✅ CreateModel
     Creating new table is safe
     model: ErrorTrackingAlertThread
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).

Last updated: 2026-09-01 23:43 UTC (7bf3f70)

@trunk-io

trunk-io Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@cat-ph
cat-ph force-pushed the cat/et-alerts-models branch 5 times, most recently from 5aec700 to 244c3c1 Compare August 28, 2026 23:42
@cat-ph
cat-ph force-pushed the cat/et-alerts-models branch from 244c3c1 to 3b493a1 Compare August 30, 2026 19:29
@cat-ph
cat-ph marked this pull request as ready for review August 31, 2026 00:44
@cat-ph
cat-ph requested review from a team, ablaszkiewicz and hpouillot August 31, 2026 00:44
@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(error_tracking): suppress tuple-retu..." | Re-trigger Greptile

@cat-ph
cat-ph force-pushed the cat/et-alerts-models branch from 2a2bbb8 to 5a458ff Compare August 31, 2026 00:54
("name", models.TextField()),
("enabled", models.BooleanField(default=True)),
("triggers", models.JSONField(default=list)),
("filters", models.JSONField(blank=True, default=dict)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if we should split issue filters from event filters

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmmmm good q, it's technically the same shape as the hog function filter, I was thinking it's simpler initially, we can discuss this maybe with the actual filter implementation in the later PR (we can also present them as split in the UI since it's a json field)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

y I think we can just split them on the UI

external_ref = models.JSONField(default=dict, blank=True)
# The root message's headline. It is the thread's identity: status transitions
# edit the root in place but never change the headline.
root_headline = models.TextField(blank=True, default="")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we need to store this ? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is technically just extra safety, for dedup, since slack messages can't really be deduplicated, if we post the message but the workflow dies, it'll double post

@cat-ph
cat-ph force-pushed the cat/et-alerts-models branch from 5a458ff to c5e5042 Compare September 1, 2026 21:52
The alert, destination, and thread models add reverse accessors to Team,
User, and Integration; the cross-boundary reverse-accessor ratchet forbids
new ones. Nothing reads them, so related_name='+' seals all five. The
unmerged 0031 migration is updated in place.
@trunk-io

trunk-io Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

😎 Stack merged successfully - details.

@trunk-io
trunk-io Bot merged commit 58fb02c into master Sep 2, 2026
240 checks passed
@trunk-io
trunk-io Bot deleted the cat/et-alerts-models branch September 2, 2026 14:18
@deployment-status-posthog

deployment-status-posthog Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-09-02 14:40 UTC Run
prod-us ✅ Deployed 2026-09-03 08:35 UTC Run
prod-eu ✅ Deployed 2026-09-03 08:36 UTC Run

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.

3 participants