Skip to content

fix(deletions): stop crashing on person-property deletion requests - #97009

Merged
trunk-io[bot] merged 1 commit into
masterfrom
haacked/deletion-gate-fix
Sep 9, 2026
Merged

trunk-io[bot] merged 1 commit into
masterfrom
haacked/deletion-gate-fix

Conversation

@haacked

@haacked haacked commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Problem

A property-removal deletion request that names a person property crashes the deletion job instead of completing or refusing cleanly, whenever flag_evaluations is in scope. The gate checks that table by querying its person_properties column for matching rows. That column holds no data the gate can trust: the producer stopped sending it, and production ClickHouse has already dropped the column in both regions. The gate only catches its own refusal error, so the raw ClickHouse "unknown identifier" error escapes as an unhandled job failure.

Changes

  • A property-removal request naming a person property against flag_evaluations now completes, or refuses cleanly on the request's event properties, instead of crashing.
  • DeletionTarget gets a stores_person_properties capability flag, false on FLAG_EVALUATIONS. The gate drops the person_properties half of its predicate for a target lacking that capability, narrowing the check rather than skipping the target.
  • The gate now builds its predicate per target through a callable, instead of sharing one predicate across every target in the list. This matches the shape the post-sweep verification check already uses.
  • Mechanical: comments on DeletionTarget and the internal coverage doc explain the new flag and its dated cause.

Warning

The flag is a static, one-time decision, not a live schema check. A flag_evaluations row written before the producer stopped sending person_properties (2026-09-05) can still hold a real value wherever the column has not been dropped, and this gate no longer sees it. Exposure is bounded by the table's TTL, up to about 120 days.

How did you test this code?

test_get_property_removal_shards_narrows_person_properties_on_flag_evaluations replaces the earlier single-case test with three parametrized cases: a person-property-only request, a mixed request that matches only on the person property, and a mixed request that matches only on the event property. Together they prove the gate narrows its check rather than skipping the target outright or suppressing the event-property half.

No test reproduces the actual production crash. The local ClickHouse schema still declares person_properties on flag_evaluations, so the unknown-identifier error this PR fixes cannot be reproduced here.

Automatic notifications

  • Publish to changelog?

Docs update

None beyond the internal coverage doc already listed in Changes.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Claude Code implemented this from a detailed task specification, using /writing-tests, /simplify, /comment-cleanup, /followup, /commit, /create-pr, and /writing-pr-descriptions, plus a code-reviewer subagent pass. Session: https://claude.ai/code/session_017RuDFm6NFyFAFxEDM79BT4

The first implementation matched the task's own sketch: a two-branch split of unsweepable targets. A four-way parallel review (reuse, simplification, efficiency, altitude) converged independently on generalizing the existing assert_no_unsweepable_rows/_refuse_unsweepable pair to take a per-target predicate callable, the same shape assert_sweep_complete already uses. That refactor shipped instead of the two-bucket special case.

The code-reviewer pass found that an early comment claiming flag_evaluations "never carries real person-property data" was not backed by git history: the column held real values for about three and a half weeks before the producer change on 2026-09-05. The comments, the coverage doc, and the regression test were rewritten to state that dated window and insert a matching row, rather than assert an unqualified "never".

  • No duplicate: searched open PRs for this crash and found none.
  • Patch coverage: not yet checked; will review the coverage comment once it posts.
  • Public artifact: nothing here beyond what's stated. The task referenced an internal review file and a sibling unmerged branch (migration 0315); neither is quoted, and that migration is out of scope for this PR.

Follow-ups filed, not done here to keep this PR scoped: logging the gate's silent skip on the audit path, adding flag_evaluations coverage to the post-sweep verification gate's tests, and considering a live system.columns probe instead of the static capability flag.

@haacked haacked self-assigned this Sep 8, 2026
@trunk-io

trunk-io Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

github-actions Bot commented Sep 8, 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.

🚨 Comment density — 17% of added code lines are comments (27 of 159)

This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.

Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.

Files with the most added comment lines:

File Comment lines Added lines
posthog/models/deletion_targets.py 15 32
posthog/dags/tests/test_data_deletion_requests.py 12 95

This check does not block merging. It updates on every push and clears when the share drops.

✅ Playwright — all passed

All tests passed.

View test results →

ℹ️ Docs preview — preview build triggered

Docs from this PR will be published at posthog.com.

Project Preview Updated (UTC)
posthog.com Open preview Sep 8, 2026, 11:32 PM

The preview should be ready in about 10 minutes. Open the preview at /handbook/engineering/.

@trunk-io

trunk-io Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@haacked
haacked force-pushed the haacked/deletion-gate-fix branch from a8e7b39 to 05adb2c Compare September 8, 2026 22:54
@haacked
haacked marked this pull request as ready for review September 8, 2026 22:58
Copilot AI lite review requested due to automatic review settings September 8, 2026 22:58
@posthog

posthog Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

🦔 PostHog Review reviewed this pull request

Found 1 must fix, 0 should fix, 0 consider.

Published 1 finding (view the review).

Resolved comments: 1 declined, 2 left for you · couldn't handle 1

@haacked haacked added the stamphog Request AI approval (no full review) label Sep 8, 2026
@haacked
haacked requested a review from orian September 8, 2026 23:00
@greptile-apps

greptile-apps Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Security Review

Property-removal requests can be reported as complete while matching legacy person-property data remains in flag_evaluations until TTL expiry on deployments where the column survives.

Prompt To Fix All With AI
### Issue 1
posthog/models/deletion_targets.py:196
**Legacy properties escape deletion**

If a deployment still retains `flag_evaluations.person_properties`, setting `stores_person_properties=False` removes the person-property criterion from both the pre-sweep and verification gates. Because `FLAG_EVALUATIONS` does not accept property rewrites, a pre-2026-09-05 row containing a requested person property can survive while the deletion request completes, leaving the data stored until TTL expiry. The capability must reflect the deployed schema or otherwise preserve a gate for these legacy rows.

**How this was verified:** The target is excluded from property rewrites, and both property-removal gates remove its person-property criterion while the repository schema and regression fixture demonstrate that the column can contain matching data.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(deletions): stop crashing on person-..." | Re-trigger Greptile

@haacked
haacked requested a review from rorylshanks September 8, 2026 23:01
Comment thread posthog/models/deletion_targets.py Resolved
@haacked

haacked commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@orian its not clear to me who owns this, but GitHub mentioned you edited these files recently so I added you as a reviewer. I added @rorylshanks since this is related to some work he helped me with recently. 😄

Copilot AI left a comment

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.

🔵 Needs a closer look

It changes safety gating in the data-deletion pipeline (ClickHouse) where subtle predicate mistakes can lead to under-deletion or incorrect refusals, so a human should do the final approval.

Pull request overview

Fixes a crash in the ClickHouse data-deletion property-removal gate when flag_evaluations is included and the (now-dropped) person_properties column is referenced, by making the gate build per-target predicates and selectively omitting person_properties checks for targets that don’t store them.

Changes:

  • Add DeletionTarget.stores_person_properties (and a __post_init__ invariant) and set it to False for FLAG_EVALUATIONS.
  • Refactor the “unsweepable rows” gate to accept a predicate_for(target) callable, enabling per-target predicate narrowing.
  • Add/adjust tests to validate the narrowed predicate behavior and update the internal coverage doc to document the new capability flag and its consequences.
File summaries
File Description
posthog/models/deletion_targets.py Adds stores_person_properties capability + invariant; marks FLAG_EVALUATIONS as not storing person properties.
posthog/dags/data_deletion_requests.py Refactors unsweepable gating to be per-target; narrows property-removal predicate for targets lacking person properties.
posthog/dags/tests/test_data_deletion_requests.py Parameterizes coverage for the narrowed gate behavior on flag_evaluations.
docs/internal/clickhouse-deletion-coverage.md Documents the new capability flag and the intentional “blind spot” tradeoff for flag_evaluations.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread posthog/models/deletion_targets.py Resolved
@veria-ai

veria-ai Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved.

Touches the personal-data deletion gate (privacy-sensitive), but the author has STRONG familiarity with exactly these files/lines (98% of touched lines, 97 merged PRs here in a year, last touch 2 days ago), the fix is narrowly scoped with new parametrized tests covering the changed behavior, and the residual blind spot is explicitly disclosed and TTL-bounded rather than hidden.

  • Author wrote 98% of the modified lines and has 97 merged PRs in these paths (familiarity STRONG).
  • 👍 on the PR from greptile-apps[bot].
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 105L, 2F substantive, 227L/4F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (227L, 4F, two-areas, fix)
stamphog 2.0.0b4 .stamphog/policy.yml @ 05adb2c · reviewed head 05adb2c

@posthog

posthog Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot left a comment

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.

PostHog Review

Found 1 must fix.

Comment thread posthog/models/deletion_targets.py Resolved
@posthog posthog Bot removed the reviewhog label Sep 8, 2026
The property-removal gate refused to run for a request naming a person
property, because it built a person_properties presence check against
flag_evaluations's copy of that column. The column carries no reachable
person-property data (its producer stopped sending it, and prod
ClickHouse has already dropped it), so the check raised an unhandled
"unknown identifier" instead of the clean refusal the gate is meant to
give.

Add DeletionTarget.stores_person_properties, false on FLAG_EVALUATIONS,
and generalize the unsweepable-rows check to build its predicate per
target via a callable, dropping the person_properties half for a
target that can't take it. Narrowing, not skipping: a request's event
properties half still gates the target as before.

A __post_init__ check on DeletionTarget now requires accepts_property_rewrite
targets to also set stores_person_properties, turning that pairing into an
import-time failure instead of an unenforced comment. A new test covers the
_event_removal_placements HogQL-refusal branch for flag_evaluations, which
had no coverage.

Claude-Session: https://claude.ai/code/session_017RuDFm6NFyFAFxEDM79BT4
@haacked
haacked force-pushed the haacked/deletion-gate-fix branch from 05adb2c to c1a8c64 Compare September 8, 2026 23:32
@trunk-io
trunk-io Bot merged commit bf0a724 into master Sep 9, 2026
276 checks passed
@trunk-io
trunk-io Bot deleted the haacked/deletion-gate-fix branch September 9, 2026 16:49
@deployment-status-posthog

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

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-09-09 17:37 UTC Run
prod-us ✅ Deployed 2026-09-09 17:57 UTC Run
prod-eu ✅ Deployed 2026-09-09 17:58 UTC Run

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

Labels

stamphog Request AI approval (no full review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants