Skip to content

fix(metrics): unblock histogram percentiles across bucket layouts - #100966

Closed
posthog[bot] wants to merge 7 commits into
masterfrom
posthog-self-driving/fixmcp-make-histogram-recovery-work-in-e667ad
Closed

posthog[bot] wants to merge 7 commits into
masterfrom
posthog-self-driving/fixmcp-make-histogram-recovery-work-in-e667ad

Conversation

@posthog

@posthog posthog Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

  • An agent investigating latency with query-metrics cannot get a histogram percentile at all: the query fails, and the recovery the tool documents does not recover it.
  • _run_histogram_quantile rejected the whole query when any two selected series used different bucket layouts, anywhere in the range.
  • The check spanned every output row, but each row already computes its quantile from its own bounds. Layouts that differ between time buckets or between groups were never a problem.
  • Series that contributed no counts still counted as a layout. One idle or reset pod on an older layout blocked every point, so no time window was narrow enough to escape it.
  • The error told the agent to narrow with filters, named no series and no layouts, and filters cannot separate two layouts inside one point anyway.

Origin

  • Scout: a custom scout
  • First signal: 2026-09-15
  • Inbox report: open
  • Task started by: a person, from the inbox

Changes

  • A histogram percentile now resolves in the cases that used to fail: a layout that changes between time buckets, and a groupBy that puts each layout in its own series.
  • The guard moved from the whole result to one point. Only a layout change inside a point makes the summed counts unusable.
  • Series with no counts no longer define the layout. groupUniqArrayIf skips them, so an idle series cannot block a query.
  • The error that remains names the point, its labels, and the layouts it mixes. It points at groupBy, which separates layouts that a filter cannot, and says where the problem lies when no attribute separates them.

histogram_quantile on a metric whose series do not all share one bucket layout:

Query Before After
Ungrouped, layout changes mid-range error one percentile per bucket
groupBy: service_name, one layout each error one percentile per service
One busy series, one idle series on an old layout error percentile from the busy series
One point, two layouts both reporting counts error, names nothing error, names both layouts and the next step
  • The MCP prompt drops the claim that all selected series must share one layout, and describes the recovery that works.
  • Mechanical: services/mcp/schema/*.json regenerated, because the prompt text is inlined there.

How did you test this code?

  • products/metrics/backend/tests/test_metric_query_runner.py ran against the sandbox ClickHouse.
  • Three new tests cover what the old guard rejected and no test held: layouts split by groupBy, a layout change between points, and an idle series on another layout.
  • The existing mismatch test now asserts the message names both layouts and groupBy. It passed before against any ValueError, including an unactionable one.
  • mypy on the changed module reports no issues. The repo-wide run did not finish in this sandbox, so CI is the check for reverse dependencies.
  • Not checked: no query ran against production metrics data. The layouts in the tests are invented.

Automatic notifications

  • Publish to changelog?

Docs update

None. The MCP prompt is the only user-facing text, and it changes in this PR.

🤖 Agent context

Autonomy: Fully autonomous

Agent: PostHog Desktop (Claude Code), Claude Opus 5

  • Skills invoked: /writing-pr-descriptions, /reviewing-with-coderabbit, /writing-simplified-technical-english.
  • CodeRabbit CLI pass: unavailable. Neither cr nor coderabbit is on PATH in this sandbox and flox is not installed, so the PR opens without a local pass.
  • Duplicate check: gh pr list --state open --search over histogram and metrics keywords found no open PR touching this guard.
  • Rejected alternative: returning a gap for a point that mixes layouts. A silent hole hides a real ingestion problem, and the response contract has no channel for a warning. The error stays, and now says what to do.
  • The layout check uses arrayExists, not arraySum. HogQL casts the counts to Nullable(Float64), and ClickHouse rejects arraySum on that.
  • /simplify dropped a second aggregate the first draft kept alongside bounds_variants, and flagged that the error copy promised a recovery that does not always exist. Both are in the final diff.

Created with PostHog Desktop from this inbox report.

Summary by CodeRabbit

  • Bug Fixes

    • Histogram queries now handle different bucket layouts independently across time points.
    • Empty or zero-count layouts no longer interfere with active histogram data.
    • Cumulative series omit transition points when bucket layouts change, then resume after a new baseline is established.
    • Conflicts within the same point provide clearer diagnostics, including affected layouts and time points.
  • Documentation

    • Updated query guidance to explain mixed histogram layouts and recommend grouping by a separating attribute or filtering to a single layout.

@trunk-io

trunk-io Bot commented Sep 15, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@posthog

posthog Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

🦔 PostHog Review reviewed this pull request

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

Published 3 findings (view the review).

Resolved comments: 3 fixed

@github-actions

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

✅ Duplication (Python) — clean

New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

✅ Duplication (TypeScript) — clean

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

🚨 Comment density — 9% of added code lines are comments (16 of 185)

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
products/metrics/backend/tests/test_metric_query_runner.py 9 127
products/metrics/backend/metric_query_runner.py 7 58

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

ℹ️ MCP UI apps size — 33 app(s), 17628.0 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 597.8 KB 196.2 KB
action 454.1 KB 196.2 KB
action-list 564.1 KB 196.2 KB
cohort 453.1 KB 196.2 KB
cohort-list 563.1 KB 196.2 KB
email-template 452.9 KB 196.2 KB
error-details 469.1 KB 196.2 KB
error-issue 453.8 KB 196.2 KB
error-issue-list 564.0 KB 196.2 KB
experiment 561.2 KB 196.2 KB
experiment-list 564.9 KB 196.2 KB
experiment-results 566.2 KB 196.2 KB
feature-flag 566.7 KB 196.2 KB
feature-flag-list 570.5 KB 196.2 KB
feature-flag-testing 457.3 KB 196.2 KB
inline-scan 453.6 KB 196.2 KB
insight-actors 562.3 KB 196.2 KB
invite-email-preview 452.3 KB 196.2 KB
llm-costs 559.3 KB 196.2 KB
session-recording 454.9 KB 196.2 KB
survey 454.7 KB 196.2 KB
survey-global-stats 561.8 KB 196.2 KB
survey-list 564.8 KB 196.2 KB
survey-stats 561.8 KB 196.2 KB
trace-span 453.5 KB 196.2 KB
trace-span-list 564.0 KB 196.2 KB
vision-observation-list 563.2 KB 196.2 KB
workflow 453.4 KB 196.2 KB
workflow-list 563.5 KB 196.2 KB
loops-review 457.8 KB 196.2 KB
query-results 773.9 KB 196.2 KB
render-ui 856.4 KB 196.2 KB
visual-review-snapshots 457.9 KB 196.2 KB
✅ ClickHouse migration SQL — none

No ClickHouse migrations in the latest push.

stamphog[bot]
stamphog Bot previously approved these changes Sep 15, 2026

@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.

A well-scoped, well-tested backend query-logic fix to an internal metrics tool (histogram percentile computation) — not auth, billing, crypto, migrations, dependencies, CI, or event ingestion. My own reading of the index/SQL changes confirms they line up correctly, and the diff matches the PR's stated behavior with new tests covering each fixed scenario.

Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 53L, 3F substantive, 117L/5F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (117L, 5F, two-areas, fix)
stamphog 2.0.0b4 .stamphog/policy.yml @ 5759aa6 · reviewed head 5759aa6

@coderabbitai

coderabbitai Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: 246aaada-d7ed-43d9-bfe0-13d4e9ca9892

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca53c8 and f68bcdc.

📒 Files selected for processing (2)
  • products/metrics/backend/metric_query_runner.py
  • products/metrics/backend/tests/test_metric_query_runner.py

Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.


📝 Walkthrough

Walkthrough

Histogram quantile processing now validates bucket layouts per output point. Empty layouts and zero-count contributions do not cause conflicts. Mixed non-empty layouts within one point raise a detailed ValueError with timestamp, labels, layouts, and grouping guidance. Cumulative layout changes produce no point until a new-layout baseline exists. Tests and query-metrics documentation cover the updated behavior.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to f68bc

The histogram layout behavior and user guidance align, with no identified merge-blocking risk.

🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description is complete and follows the repository template. It explains the problem, user-visible changes, testing performed and limitations, notification and documentation status, a…
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch posthog-self-driving/fixmcp-make-histogram-recovery-work-in-e667ad
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch posthog-self-driving/fixmcp-make-histogram-recovery-work-in-e667ad

Comment @coderabbitai help to get the list of available commands.

@posthog

posthog Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

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 Author

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, 1 should fix, 1 consider.

Comment thread products/metrics/backend/metric_query_runner.py
Comment thread products/metrics/backend/metric_query_runner.py Outdated
Comment thread products/metrics/backend/metric_query_runner.py Outdated
@stamphog
stamphog Bot dismissed their stale review September 15, 2026 13:32

A new stamphog review started for this PR — the fresh verdict replaces this approval.

@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.

Not approved — this change needs a human reviewer.

Re-add the stamphog label to request another review once you have addressed this.

The posthog[bot] review has 3 unresolved threads; the most severe (cumulative counters subtracting across a layout change) does appear fixed by the new prev_bounds check in the diff, but two remain unaddressed — one where the mismatch error can show byte-identical text for two distinct layouts, and one where a histogram with empty explicit_bounds is silently excluded from the layout guard and could combine with another layout's counts into a wrong percentile. That second point is a real silent-corruption risk in a query path, not just cosmetic.

  • Unresolved should_fix (posthog[bot], metric_query_runner.py): _render_bounds truncates to 6 significant digits, so two distinct bucket layouts can render identical text in the mismatch error, defeating the diagnostic's purpose.
  • Unresolved consider (posthog[bot], metric_query_runner.py): the if variant filter drops empty-bounds layouts from the guard's layouts set, so an active OTel histogram with no explicit bounds can combine its counts with another layout undetected and return a false percentile — cited against real ingestion behavior and an existing 'no_bounds' test fixture.
Gate mechanics and policy version
Gate Result
prerequisites ✓ all clear
deny-list ✓ no deny categories matched
size ✓ 67L, 3F substantive, 165L/5F incl. docs/generated/snapshots — within ceiling
tier ✓ T1-agent / T1c-medium (165L, 5F, two-areas, fix)
stamphog 2.0.0b4 .stamphog/policy.yml @ 4ca53c8 · reviewed head 4ca53c8

@stamphog stamphog Bot added the reviewhog ($$$) Reviews pull requests before humans do label Sep 15, 2026
@DanielVisca
DanielVisca marked this pull request as ready for review September 16, 2026 13:23
@DanielVisca
DanielVisca self-requested a review September 16, 2026 13:23
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team September 16, 2026 13:23
A histogram_quantile query failed whenever any two selected series used
different bucket layouts, anywhere in the range. Each output point already
computes its quantile from its own bounds, so layouts that differ between
time buckets or between groups are harmless. Only a layout change inside
one point makes the summed counts unusable.

Series that contribute no counts no longer define or challenge the layout,
so an idle or reset series cannot block a query.

The error now names the point, its labels, and the layouts it mixes, and
points at group_by, which separates layouts that a filter cannot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 931ffce0-d0d8-4ee7-abd6-6e1a8126a591
The query-metrics prompt text is inlined into the generated tool
definitions, so the histogram guidance change has to land there too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 931ffce0-d0d8-4ee7-abd6-6e1a8126a591
The per-point check leaves exactly one layout, so a separate anyIf column
carried no information the variants column did not already hold. Dropping
it removes an aggregate and one positional offset.

The error no longer promises that an attribute always separates two
layouts. When none does, it says where the problem actually lies.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 931ffce0-d0d8-4ee7-abd6-6e1a8126a591
The cumulative path lagged the counts but not the bounds, and the series
identity excludes the bucket layout. A layout change under one identity
therefore subtracted counts from unrelated buckets: with an equal bucket
count element by element, and otherwise by treating the whole cumulative
vector as the increase. Both produced a percentile with no error.

The per-point guard cannot see this, because a change that falls on a
bucket boundary leaves each point holding one layout.

The window now lags the bounds too. A sample whose layout differs from
its predecessor contributes nothing, the same as a sample with no
predecessor at all, so the point shows a gap and the next sample of the
new layout resolves normally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 57b727df-0f85-4977-b5f0-209446feaa05
The message named the layouts it found, but two distinct layouts could
render as the same text. Six shown bounds hid a change further along, and
`:g` rounded away a difference past the sixth significant digit. The error
then claimed two layouts and printed one string twice, which leaves an
agent with the unactionable error this work removes.

Bounds now print with round-trip formatting, and a layout that shares its
head with another also names the bound that separates it.

The length of the bounds is reported as boundaries, not buckets. Explicit
buckets carry one more count than bounds and flattened exponential
buckets carry one each, so the bounds alone cannot give a bucket count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 57b727df-0f85-4977-b5f0-209446feaa05
Ingestion accepts a histogram whose bounds are empty and whose single
overflow count is positive. The guard dropped that empty layout before it
compared layouts, so a point holding both an unbucketed series and a
bucketed one raised nothing. The overflow count then landed in the first
bucket of a layout it never belonged to, and the point returned a
percentile built from both.

The guard now counts an empty layout, so such a point reports the
mismatch and names both layouts. A point where the unbucketed series is
alone is unchanged.

The test helper no longer folds empty bounds into the default layout, so
a test can seed the unbucketed shape.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 57b727df-0f85-4977-b5f0-209446feaa05
Resolving the merge with master dropped the leading bounds column from the histogram query, but kept master's row[4 + group_count] for series_fingerprints. It is the third column after time, so read it at row[3 + group_count].
@DanielVisca
DanielVisca force-pushed the posthog-self-driving/fixmcp-make-histogram-recovery-work-in-e667ad branch from f68bcdc to 4510edb Compare September 16, 2026 13:35
@trunk-io

trunk-io Bot commented Sep 16, 2026

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@scheduled-actions-posthog

Copy link
Copy Markdown
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, please remove the stale label – otherwise this will be closed in another week. If you want to permanently keep it open, use the waiting label.

@scheduled-actions-posthog

Copy link
Copy Markdown
Contributor

This PR was closed due to lack of activity. Feel free to reopen if it's still relevant.

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

Labels

reviewhog ($$$) Reviews pull requests before humans do stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant