Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
d3ec5e2
v1
charlesvien Jul 8, 2026
bdf4f42
v2
charlesvien Jul 8, 2026
104508e
feat(tasks): add loops cloud agent automations
charlesvien Jul 8, 2026
d48aa04
chore(tasks): add language to loops doc code fence
charlesvien Jul 14, 2026
8b3b2fe
chore(tasks): format loops docs with oxfmt
charlesvien Jul 14, 2026
94a18d6
fix(tasks): resolve loops mypy errors
charlesvien Jul 14, 2026
53cdfdd
chore(tasks): cover loop models in idor rules
charlesvien Jul 14, 2026
2bd050d
fix(tasks): route loop wiring through facade
charlesvien Jul 14, 2026
ba139a7
test(mcp): update unit test snapshots
tests-posthog[bot] Jul 14, 2026
39d1129
chore: update OpenAPI generated types
tests-posthog[bot] Jul 14, 2026
dd1413e
fix(tasks): enforce loop trigger payload cap on parsed body
charlesvien Jul 14, 2026
b599312
feat(tasks): auto-select loop model when left unset
charlesvien Jul 14, 2026
3a2e671
test(tasks): prove loop behaviors round-trip through the api
charlesvien Jul 14, 2026
cc3d50a
Create LOOPS-ONE-OFF-TASKS.md
charlesvien Jul 14, 2026
e5fe3cb
test(mcp): update unit test snapshots
tests-posthog[bot] Jul 14, 2026
ed1b699
feat(tasks): cap loops per team and triggers per loop to prevent abuse
charlesvien Jul 14, 2026
b45b2ad
feat(tasks): add internal and origin_product to loops
charlesvien Jul 14, 2026
3a0c0b2
feat(tasks): let loops attach to a context and maintain it
charlesvien Jul 15, 2026
9196953
chore(tasks): add loops primitives audit doc
charlesvien Jul 15, 2026
6e7be15
fix(tasks): close loop cross-tenant and visibility holes
charlesvien Jul 15, 2026
a9c3d71
fix(tasks): keep loop schedules in sync with the DB
charlesvien Jul 15, 2026
c75ded4
fix(tasks): make loop firing idempotent and overlap-safe
charlesvien Jul 15, 2026
11f013d
fix(tasks): harden loop facade contract and lifecycle
charlesvien Jul 15, 2026
72bd294
feat(tasks): loop DX, PSAK readback, ownership takeover, MCP tools
charlesvien Jul 15, 2026
1ef20b1
chore(tasks): loop github filter tests and doc fixups
charlesvien Jul 15, 2026
55b4aa4
updates
charlesvien Jul 15, 2026
90e31fb
add doc for loop context.md and canvas outputs
charlesvien Jul 15, 2026
353be0b
mcp renderering
charlesvien Jul 15, 2026
0c13e40
test(mcp): update unit test snapshots
tests-posthog[bot] Jul 15, 2026
f2bd42f
chore: update OpenAPI generated types
tests-posthog[bot] Jul 15, 2026
025cea8
test fixes
charlesvien Jul 15, 2026
d62b3e7
Update LoopReviewView.tsx
charlesvien Jul 16, 2026
84b4878
reclaim loop run sandbox promptly when idle
charlesvien Jul 16, 2026
c23632c
terminalize background runs on their natural end
charlesvien Jul 16, 2026
63d67d0
tell loop runs to finish via the finish tool
charlesvien Jul 16, 2026
7cf643a
build loop table indexes concurrently
charlesvien Jul 16, 2026
1e560c8
test
charlesvien Jul 16, 2026
ca30d33
Update 0061_loop_looptrigger_loopfire.py
charlesvien Jul 16, 2026
2b17496
re-run ci
charlesvien Jul 16, 2026
ab76d54
add loop scope to access control inline types
charlesvien Jul 16, 2026
6ee3405
reap stale loop runs to unblock new fires
charlesvien Jul 17, 2026
08ecec6
make loops-create validation forgiving and clear
charlesvien Jul 17, 2026
0babe49
redesign loop review card header and errors
charlesvien Jul 17, 2026
0d86bc5
fixe
charlesvien Jul 17, 2026
d25dc5b
wire loop bookkeeping into workflow terminal path
charlesvien Jul 17, 2026
05827c5
persist loop dispatch params for the reconciler
charlesvien Jul 17, 2026
4e078ee
release webhook dedup key when a handler fails
charlesvien Jul 17, 2026
c31da65
harden loop sweeps, quota and schedule syncing
charlesvien Jul 17, 2026
18c2d05
fix loops-create repo cap in tool description
charlesvien Jul 17, 2026
bee7918
revert hand-edits to generated kea blocks
charlesvien Jul 17, 2026
a70ed40
chore(tasks): reorganize loops docs
charlesvien Jul 17, 2026
dd0df32
fix(tasks): tear down spent loop schedules, surface loop cap
charlesvien Jul 17, 2026
ab6bc93
feat(tasks): raise per-project loop cap to 100
charlesvien Jul 17, 2026
744a63e
fix(tasks): add loop to generated access control types
charlesvien Jul 18, 2026
946734e
fix(tasks): eliminate zombie loop temporal schedules
charlesvien Jul 18, 2026
6d0206f
fix(tasks): resolve critical loop write, run and audit-log bugs
charlesvien Jul 18, 2026
2a586aa
fix(tasks): harden loop retention, reconciliation and create-cap races
charlesvien Jul 18, 2026
75465d9
chore(tasks): document deferred loop hardening and next steps
charlesvien Jul 18, 2026
73753d8
chore: clear mypy errors blocking the loops PR
charlesvien Jul 18, 2026
b210dcc
Update converters.py
charlesvien Jul 18, 2026
e19ba3a
chore(tasks): regenerate mcp types after rebase
charlesvien Jul 21, 2026
5ad063b
chore(toolbar): raise graph budget for loop scope strings
charlesvien Jul 21, 2026
6dc93f9
fix(tasks): scope non-PSAK loop triggers to visible loops
charlesvien Jul 21, 2026
f581afc
fix(tasks): recheck loop enabled under the fire lock
charlesvien Jul 21, 2026
527f881
test(mcp): expect loops flag in tool filtering
charlesvien Jul 21, 2026
df30d77
fix(tasks): add loops flag to mcp tool test fixtures
charlesvien Jul 21, 2026
a707f6c
fix(tasks): count only dispatched fires toward rate caps
charlesvien Jul 21, 2026
68fd0d4
fix(tasks): cancel deactivated user's runs on transferred loops
charlesvien Jul 21, 2026
1196cbb
fix(tasks): enforce loop connector allowlist in sandbox runs
charlesvien Jul 21, 2026
ef77f5c
fix(tasks): require typed confirmation to create a loop via mcp
charlesvien Jul 21, 2026
af1160c
fix(tasks): bind loop repos to integration, gate destructive ops on c…
charlesvien Jul 21, 2026
2f725ba
fix(tasks): never fire a loop whose owner is deactivated
charlesvien Jul 21, 2026
96ece8b
fix(tasks): fail closed when a loop repo can't be verified
charlesvien Jul 21, 2026
18569a9
fix(tasks): re-check loop owner active state under the fire lock
charlesvien Jul 21, 2026
bd261bf
fix(tasks): require current org membership to fire a loop
charlesvien Jul 21, 2026
c5a2b91
fix(tasks): offboard loops when a member is removed from the org
charlesvien Jul 21, 2026
3007fa5
fix(tasks): only fire github-triggered loops for trusted actors
charlesvien Jul 21, 2026
f1bf910
revert(tasks): drop loops-create confirmed_action, it broke the revie…
charlesvien Jul 21, 2026
5fddfe9
fix(tasks): drop push commit messages from loop trigger context
charlesvien Jul 21, 2026
82e4c1b
fix(tasks): fail closed minting loop credentials for ineligible owner
charlesvien Jul 21, 2026
c1cd420
fix(tasks): re-read and lock loop owner eligibility at credential mint
charlesvien Jul 21, 2026
b959149
fix(tasks): gate loop credentials on team access, incl github tokens
charlesvien Jul 21, 2026
71f41cc
fix(tasks): restrict non-psak loop triggering to the owner
charlesvien Jul 21, 2026
a212a9a
fix(tasks): lock org membership while minting loop credentials
charlesvien Jul 21, 2026
ba8e627
fix(tasks): fail closed on empty connector allowlist, recheck github …
charlesvien Jul 21, 2026
b4840b2
fix(tasks): require content-length to bound loop trigger parsing
charlesvien Jul 21, 2026
55a31c4
fix(tasks): throttle loop github events and bound capped-fire writes
charlesvien Jul 21, 2026
e1ea1d3
fix(tasks): enforce object-level rbac on loop api paths
charlesvien Jul 21, 2026
4584f2e
fix(tasks): hide deleted personal loop activity org-wide
charlesvien Jul 21, 2026
ac6f1c9
fix(tasks): gate loop github refresh paths on owner eligibility
charlesvien Jul 21, 2026
3ab6854
fix(tasks): abort loop fire when owner changed after usage gate
charlesvien Jul 21, 2026
15dd92d
fix(tasks): require confirmation to create loops via mcp
charlesvien Jul 21, 2026
35f0602
chore: drop unused type ignore in personhog converters
charlesvien Jul 21, 2026
39c3515
chore: update OpenAPI generated types
tests-posthog[bot] Jul 21, 2026
20372c0
fix(tasks): apply loop rbac to activity log visibility
charlesvien Jul 21, 2026
4a3d43d
chore(tasks): document mcp loop-create confirmation tradeoff
charlesvien Jul 21, 2026
acaac20
chore: fix mypy errors surfaced by ci environment
charlesvien Jul 21, 2026
1a26542
fix(tasks): split loop creator backfill from schema migration
charlesvien Jul 21, 2026
5fa5ca8
chore: scope cache signal test to its own on_commit call
charlesvien Jul 21, 2026
d28928e
chore: record loop receivers in setup baseline
charlesvien Jul 21, 2026
7b0c2e8
chore(tasks): expect connector allowlist kwarg in followup test
charlesvien Jul 21, 2026
0c00c1e
fix(tasks): gate loops-review mcp tool behind loops flag
charlesvien Jul 21, 2026
ba0e40a
fix(tasks): run loop bookkeeping when workflow start fails
charlesvien Jul 21, 2026
161c337
fix(tasks): capture task_deleted via scoped client in sweep
charlesvien Jul 21, 2026
71ec282
chore: update OpenAPI generated types
tests-posthog[bot] Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .semgrep/rules/security/idor-team-scoped-models.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ rules:
|LogsExclusionRule
|LogsMetricRule
|LogsView
|Loop
|LoopFire
|LoopTrigger
|LLMModelConfiguration
|LLMPrompt
|LLMPromptLabel
Expand Down Expand Up @@ -493,6 +496,9 @@ rules:
|KnowledgeSource
|Link
|LiveDebuggerBreakpoint
|Loop
|LoopFire
|LoopTrigger
|LLMModelConfiguration
|LLMPrompt
|LLMPromptLabel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ export interface accessControlsLogicActions {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -503,6 +504,7 @@ export interface accessControlsLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -643,6 +645,7 @@ export interface accessControlsLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -756,6 +759,7 @@ export interface accessControlsLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -205,6 +206,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -314,6 +316,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -436,6 +439,7 @@ export interface groupedAccessControlRuleModalLogicValues {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -558,6 +562,7 @@ export interface groupedAccessControlRuleModalLogicActions {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -687,6 +692,7 @@ export interface groupedAccessControlRuleModalLogicActions {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -796,6 +802,7 @@ export interface groupedAccessControlRuleModalLogicActions {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -944,6 +951,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -1055,6 +1063,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -1165,6 +1174,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -1275,6 +1285,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -1386,6 +1397,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -1496,6 +1508,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -1617,6 +1630,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -1727,6 +1741,7 @@ export interface groupedAccessControlRuleModalLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ export interface accessControlLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -577,6 +578,7 @@ export interface accessControlLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down Expand Up @@ -687,6 +689,7 @@ export interface accessControlLogicMeta {
| 'llm_provider_key'
| 'llm_skill'
| 'logs'
| 'loop'
| 'marketing_analytics'
| 'mcp_analytics'
| 'metrics'
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/agentScopes.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export const AGENT_USE_CASE_SCOPES = [
'llm_skill:write',
'logs:read',
'logs:write',
'loop:read',
'loop:write',
'marketing_analytics:read',
'mcp_analytics:read',
'mcp_analytics:write',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lib/scopes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export const API_SCOPES: APIScope[] = [
{ key: 'llm_provider_key', objectName: 'LLM provider key', objectPlural: 'LLM provider keys' },
{ key: 'llm_skill', objectName: 'LLM skill', objectPlural: 'LLM skills' },
{ key: 'logs', objectName: 'Logs', objectPlural: 'logs' },
{ key: 'loop', objectName: 'Loop', objectPlural: 'loops' },
{ key: 'marketing_analytics', objectName: 'Marketing analytics', objectPlural: 'marketing analytics' },
{ key: 'mcp_analytics', objectName: 'MCP analytics', objectPlural: 'MCP analytics' },
{ key: 'metrics', objectName: 'Metrics', objectPlural: 'metrics' },
Expand Down Expand Up @@ -257,6 +258,7 @@ export const PROJECT_SECRET_API_KEY_ALLOWED_API_SCOPE_ACTION = [
'endpoint:read',
'feature_flag:read',
'account:read',
'loop:write',
] as const

export type ProjectSecretAPIKeyAllowedScope = (typeof PROJECT_SECRET_API_KEY_ALLOWED_API_SCOPE_ACTION)[number]
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5740,6 +5740,7 @@ export const API_SCOPE_OBJECTS = [
'llm_provider_key',
'llm_skill',
'logs',
'loop',
'marketing_analytics',
'mcp_analytics',
'metrics',
Expand Down
46 changes: 46 additions & 0 deletions posthog/api/advanced_activity_logs/viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,48 @@
from .utils import get_activity_log_lookback_restriction


def restrict_loop_activity(queryset: QuerySet[ActivityLog], team_id: int, user) -> QuerySet[ActivityLog]:
"""Keep personal loops' config out of the team-wide activity feed.

Loop activity is team-scoped in the log, but a personal loop is owner-only (see
products/tasks/docs/LOOPS.md "Access control"). The static visibility manager can't express
per-user ownership, so restrict `Loop`-scoped rows to the loops this user may actually see.
Lazy import keeps the tasks product off this module's import path.
"""
from products.tasks.backend.facade import loops as loops_facade # noqa: PLC0415
Comment thread
charlesvien marked this conversation as resolved.

visible_ids = loops_facade.visible_loop_ids(team_id, user)
return queryset.exclude(Q(scope="Loop") & ~Q(item_id__in=visible_ids))


def restrict_loop_activity_for_org(queryset: QuerySet[ActivityLog], organization_id, user) -> QuerySet[ActivityLog]:
"""Org-wide equivalent of `restrict_loop_activity`. The org route has no single `team_id`, so it
can't build a per-team allowlist; instead deny other users' personal-loop rows across the org.

Two filters, both required. The persisted per-row context (`detail.context.visibility` /
`created_by_user_id`, snapshotted at log time) is the primary one: `ActivityLog` outlives its
loop (project deletion cascades `Loop` rows away while the log keeps plain `team_id` /
`organization_id`), so a live-row denylist alone would open another user's deleted personal-loop
history to org admins. The live-row denylist stays on top so a currently-personal loop hides ALL
its rows, including ones logged back when it was team-visible.

No object-level loop RBAC here, deliberately: this route is restricted to org admins and owners
(`OrganizationActivityLogPermission`), who pass the RBAC precheck for every object, so the
filter the team route applies via `visible_loop_ids` would be a no-op on this one."""
from products.tasks.backend.facade import loops as loops_facade # noqa: PLC0415

user_id = getattr(user, "id", None)
persisted_personal = Q(scope="Loop") & Q(detail__context__visibility="personal")
if user_id is not None:
persisted_personal &= ~Q(detail__context__created_by_user_id=str(user_id))
queryset = queryset.exclude(persisted_personal)

hidden_ids = loops_facade.hidden_personal_loop_ids_for_org(organization_id, user)
Comment thread
charlesvien marked this conversation as resolved.
if not hidden_ids:
return queryset
return queryset.exclude(Q(scope="Loop") & Q(item_id__in=hidden_ids))


def apply_organization_scoped_filter(
queryset: QuerySet[ActivityLog], include_org_scoped: bool, team_id: int, organization_id
) -> QuerySet[ActivityLog]:
Expand Down Expand Up @@ -192,6 +234,7 @@ def safely_get_queryset(self, queryset) -> QuerySet:
queryset = queryset.filter(created_at__gte=lookback_date)

queryset = apply_activity_visibility_restrictions(queryset, self.request.user)
queryset = restrict_loop_activity(queryset, self.team_id, self.request.user)

return queryset

Expand Down Expand Up @@ -438,6 +481,7 @@ def safely_get_queryset(self, queryset) -> QuerySet:
queryset = queryset.filter(created_at__gte=lookback_date)

queryset = apply_activity_visibility_restrictions(queryset, self.request.user)
queryset = restrict_loop_activity(queryset, self.team_id, self.request.user)

return queryset.order_by("-created_at")

Expand Down Expand Up @@ -584,6 +628,8 @@ def safely_get_queryset(self, queryset) -> QuerySet:
queryset = queryset.filter(created_at__gte=lookback_date)

queryset = apply_activity_visibility_restrictions(queryset, self.request.user)
# Org route: no single team_id (this endpoint is org-nested), so use the org-wide variant.
queryset = restrict_loop_activity_for_org(queryset, self.organization.id, self.request.user)

return queryset.order_by("-created_at")

Expand Down
29 changes: 29 additions & 0 deletions posthog/api/github_callback/installation_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

The handler never calls GitHub's DELETE endpoint — the App is already gone —
which keeps the outbound/inbound pair loop-free.

Before the Integration rows are deleted, any Loop (products/tasks) referencing them is
auto-paused and flagged for attention, so a disconnected integration never leaves a loop
silently pointed at a repository it can no longer reach.
"""

from django.http import HttpResponse
Expand Down Expand Up @@ -38,6 +42,9 @@ def handle_installation_event(payload: dict) -> HttpResponse:

installation_id = str(installation_id)

integrations = list(Integration.objects.filter(kind="github", integration_id=installation_id))
_pause_loops_referencing_integrations(integrations, installation_id)

team_deleted, _ = Integration.objects.filter(kind="github", integration_id=installation_id).delete()
user_deleted, _ = UserIntegration.objects.filter(kind="github", integration_id=installation_id).delete()

Expand All @@ -49,3 +56,25 @@ def handle_installation_event(payload: dict) -> HttpResponse:
)

return HttpResponse(status=200)


def _pause_loops_referencing_integrations(integrations: list[Integration], installation_id: str) -> None:
"""Auto-pause every loop referencing a GitHub integration that's about to be hard-deleted.

See products/tasks/docs/LOOPS.md "Lifecycle and reconciliation": the App uninstall hard-deletes
the Integration row with no downstream hooks, and loop references to it are JSON, so no FK
machinery helps. Runs before the delete below and is fully isolated: a loops-side failure must
never break the pre-existing Integration/UserIntegration deletion path.
"""
if not integrations:
return

try:
from products.tasks.backend.facade.loops import ( # noqa: PLC0415 (keeps the loops/Temporal dependency off this module's import path)
pause_loops_referencing_integrations,
)
except Exception:
logger.exception("github_installation_webhook_loop_import_failed", installation_id=installation_id)
return

pause_loops_referencing_integrations(integrations, installation_id)
1 change: 1 addition & 0 deletions posthog/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ def is_email_available(with_absolute_urls: bool = False) -> bool:
"integration_access_requested": "70",
"posthog_ai_access_requested": "72",
"wizard_pr_ready": "74",
"loop_run_summary": "73", # placeholder id, needs creating in Customer.io
}


Expand Down
Loading
Loading