Skip to content

fix(aio): stop dropping posthog_ custom properties on otel paths - #73578

Merged
Radu-Raicea merged 2 commits into
masterfrom
fix(aio)/otel-passthrough-custom-metadata
Jul 24, 2026
Merged

fix(aio): stop dropping posthog_ custom properties on otel paths#73578
Radu-Raicea merged 2 commits into
masterfrom
fix(aio)/otel-passthrough-custom-metadata

Conversation

@carlos-marchal-ph

Copy link
Copy Markdown
Contributor

Problem

Custom properties sent as posthog_-prefixed OTel metadata were silently dropped on two AI observability ingestion paths.
The Vercel AI SDK path strips the whole ai.telemetry.metadata.* namespace, and the Traceloop/OpenLLMetry path strips traceloop.association.properties.*.
Both strips ran with only a narrow allowlist, so anyone attaching custom dimensions (the mechanism people use to filter generations, e.g. tags or environment) lost them.

The generic OTel and Pydantic AI paths already forward custom attributes as-is, so this was an inconsistency rather than intended behavior: posthog_-namespaced metadata is meant to survive.

Origin: internal thread.

Changes

  • New shared helper promotePosthogCustomMetadata(props, namespacePrefix).
    It promotes <namespace>posthog_<name> to a top-level <name> property (prefix stripped), only when <name> is unset, skipping $-prefixed keys and distinct_id so custom metadata can't clobber reserved properties or the event distinct id.
  • Wired into both stripping middlewares, before their strip runs: ai.telemetry.metadata. (Vercel) and traceloop.association.properties. (Traceloop).
  • Generic OTel and Pydantic paths untouched — they already pass custom attributes through unprefixed.
  • No change to reserved-key handling: posthog_distinct_id keeps its existing identity treatment.

Because the promotion runs per event, custom properties land on each $ai_generation, so generations are individually filterable.

How did you test this code?

Automated only. I (Claude) ran these; no manual or end-to-end testing was done.

  • New unit test on the helper covering the guard matrix: prefix strip (string and array values), non-posthog_ keys ignored, $-prefixed skipped, distinct_id skipped, only-if-undefined.
  • One wiring test per middleware proving the promotion runs before the strip: Vercel ai.telemetry.metadata.posthog_tagstags, Traceloop traceloop.association.properties.posthog_envenv.
  • Full nodejs/src/ingestion/pipelines/ai/otel/ suite: 208 passing (6 suites). Prettier clean. tsc --noEmit clean for the changed files (only pre-existing, unrelated @posthog/replay-anonymizer module-resolution errors remain in this environment).

The changed lines are covered by the tests above.

👉 Stay up-to-date with PostHog coding conventions for a smoother review.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

No user-facing doc change. This restores expected behavior (posthog_ metadata passing through), so there is nothing new to document. Safe to add skip-inkeep-docs.

🤖 Agent context

Autonomy: Human-driven (agent-assisted) — I directed the work, so assign me as the DRI.

Built with Claude Code (Opus). Skills invoked: /writing-tests (to gate the test additions) and /pr-link (for this PR link).

The work started scoped to the Vercel path only, matching the incoming request. While verifying I checked all three middlewares plus the generic mapper and found Traceloop blanket-strips its own custom-metadata namespace (traceloop.association.properties.*) the same way Vercel does, while the generic OTel and Pydantic paths already forward custom attributes. So the proper fix generalizes to both stripping paths, which is why the promotion moved into a shared helper wired into each. I deliberately left the pass-through paths alone rather than forcing a uniform posthog_ rename that would only break existing property names.

I first framed this as a feature and added an onboarding-doc note, then reverted it: posthog_ properties surviving the strip is expected behavior, so this is a bug fix, not a documented feature.

@carlos-marchal-ph carlos-marchal-ph self-assigned this Jul 24, 2026
@carlos-marchal-ph carlos-marchal-ph added the stamphog Request AI approval (no full review) label Jul 24, 2026
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
nodejs/src/ingestion/pipelines/ai/otel/middleware/custom-metadata.ts:14-16
**Inherited names block promotion**

For metadata names such as `posthog_constructor` or `posthog_toString`, this lookup resolves the inherited `Object.prototype` member and treats it as an existing property. The middleware then strips the namespaced source key, silently discarding that custom dimension; check for an existing own property instead.

Reviews (1): Last reviewed commit: "fix: otel passthrough custom metadata" | Re-trigger Greptile

Comment thread nodejs/src/ingestion/pipelines/ai/otel/middleware/custom-metadata.ts Outdated
@stamphog

stamphog Bot commented Jul 24, 2026

Copy link
Copy Markdown

Note

🤖 stamphog reviewed 18b9cc349b9f1536a6d2d8816f80d4853698e3d4 — verdict: ESCALATE

This modifies core AI-observability event ingestion middleware (property promotion run on every OTel-based event before stripping), which is risky ingestion-path territory; the author is not on the owning ingestion team and the only review present is Greptile's, left on an older commit — the fix (own-property check + proto guard) looks correct on inspection, but there is no current-head approval/comment confirming it.

  • 👍 on the PR from greptile-apps[bot], hex-security-app[bot].
  • Change lands in the AI observability event ingestion pipeline (risky territory) and is authored by someone outside @PostHog/team-ingestion.
  • The only review (Greptile, COMMENTED) is on an older commit (a4fc0f2), not the current head — the flagged prototype-chain bug appears fixed in the latest diff (hasOwnProperty check + proto added to reserved names, with new tests), but no reviewer has re-confirmed the fix on the current head.
  • No STRONG/MODERATE author familiarity signal was reported for this path.
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 23L, 3F substantive, 83L/6F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1c-medium (83L, 6F, single-area, fix)
stamphog 2.0.0b3 .stamphog/policy.yml @ d1dd198 · reviewed head 18b9cc3

Updated in place — this replaces 1 earlier stamphog review(s) on this PR.

@stamphog stamphog Bot removed the stamphog Request AI approval (no full review) label Jul 24, 2026
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

The bug is real. On a plain object, props['constructor'] resolves to Object via the prototype chain — not undefined — so posthog_constructor is silently dropped instead of promoted. Same for toString, valueOf, and other Object.prototype members.

The fix is straightforward — replace the === undefined check with an own-property check:

        if (!Object.prototype.hasOwnProperty.call(props, name)) {
            props[name] = props[key]
        }

This correctly allows promotion only when name is not already an own property of props, regardless of what prototype-inherited members exist. You should also add a test case to custom-metadata.test.ts for this:

it('promotes posthog_constructor metadata (not blocked by prototype chain)', () => {
    const props: Record<string, unknown> = { 'ns.posthog_constructor': 'my-value' }
    promotePosthogCustomMetadata(props, 'ns.')
    expect(props['constructor']).toBe('my-value')
})

@carlos-marchal-ph carlos-marchal-ph added the stamphog Request AI approval (no full review) label Jul 24, 2026
@stamphog stamphog Bot removed the stamphog Request AI approval (no full review) label Jul 24, 2026
@Radu-Raicea
Radu-Raicea merged commit fc211d0 into master Jul 24, 2026
309 of 310 checks passed
@Radu-Raicea
Radu-Raicea deleted the fix(aio)/otel-passthrough-custom-metadata branch July 24, 2026 17:04
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 24, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-24 17:27 UTC Run
prod-us ✅ Deployed 2026-07-24 17:47 UTC Run
prod-eu ✅ Deployed 2026-07-24 17:48 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.

2 participants