Skip to content

fix(posthog): validate integration against live API docs, add self-hosted support + CRUD coverage#5476

Merged
waleedlatif1 merged 6 commits into
stagingfrom
validate/posthog-integration
Jul 7, 2026
Merged

fix(posthog): validate integration against live API docs, add self-hosted support + CRUD coverage#5476
waleedlatif1 merged 6 commits into
stagingfrom
validate/posthog-integration

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

  • Fixed ~36 tools silently treating error response bodies as success data (missing response.ok checks)
  • Fixed delete_feature_flag returning success: true regardless of actual API response
  • Fixed evaluate_flags targeting the undocumented /decide endpoint and missing the required api_key body field; migrated to the documented /flags/?v=2 endpoint
  • Fixed batch_events reporting a fabricated events_processed count instead of the actual batch size
  • Added self-hosted PostHog host support (utils.ts) alongside the existing US/EU region selector
  • Added 5 new tools to complete CRUD coverage: update_insight, update_cohort, update_experiment, delete_survey, create_dashboard

Type of Change

  • Bug fix
  • New feature (endpoint coverage)

Testing

Validated every tool against PostHog's live API docs. tsc --noEmit and bunx biome check both clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…sted support + CRUD coverage

Fix missing response.ok checks across ~36 tools that silently treated
error bodies as success. Fix delete_feature_flag ignoring failure
responses, evaluate_flags targeting the undocumented /decide endpoint
without the required api_key body field, and batch_events reporting a
hardcoded events_processed count.

Add self-hosted host support (utils.ts) alongside the existing US/EU
region selector, and complete CRUD coverage with 5 new tools:
update_insight, update_cohort, update_experiment, delete_survey,
create_dashboard.
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jul 7, 2026 5:20pm

Request Review

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Wide integration surface with destructive deletes and changed HTTP semantics; workflows depending on old endpoints or response shapes may break, though scope is limited to PostHog tooling.

Overview
Expands the PostHog block and tool suite with self-hosted instance support via an optional Self-Hosted Host field (overrides US/EU), centralized URL helpers, and a dedicated posthog-errors extractor for API detail/attr messages.

Adds update insight/cohort/experiment, create dashboard, and delete survey operations, with matching block fields (e.g. favorited, pinned, archive cohort/experiment).

Aligns several tools with documented PostHog behavior: feature flag delete is a soft-delete PATCH; person delete uses bulk_delete; evaluate flags calls /flags/?v=2 with api_key in the body; ingestion tools treat status === 1 as success and validate batch/properties JSON strictly. Insights lean on query (legacy filters dropped from create/get paths); experiments drop the variants field; annotations use dashboard_id where appropriate.

Reviewed by Cursor Bugbot for commit 98d6feb. Configure here.

Comment thread apps/sim/tools/posthog/evaluate_flags.ts
Comment thread apps/sim/tools/posthog/evaluate_flags.ts
@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens the PostHog integration in three areas: fixes ~36 tools that were silently treating error responses as success data, migrates evaluate_flags from the undocumented /decide endpoint to the documented /flags/?v=2 endpoint (with the required api_key body field), and adds five new tools for full CRUD coverage alongside self-hosted host support.

  • Bug fixes: Removed response.ok checks that masked HTTP errors; fixed delete_feature_flag soft-delete via PATCH {deleted:true} (hard DELETE returns 405); corrected batch_events to report the actual batch size rather than a fabricated count; changed delete_person to use the bulk_delete endpoint with persons_deleted success detection.
  • New tools: update_insight, update_cohort, update_experiment, delete_survey, create_dashboard — all with SSRF-guarded self-hosted host support and posthog-errors error extractor.
  • Self-hosted support: New utils.ts with getPostHogAppBaseUrl/getPostHogIngestBaseUrl helpers that route through validateExternalUrl to block SSRF targets.

Confidence Score: 5/5

Safe to merge — the fixes are well-scoped and the endpoint migration is validated against live PostHog docs.

The core changes (removing incorrect response.ok guards, the evaluate_flags endpoint migration, and the new CRUD tools) are all correct. The SSRF guard for self-hosted hosts is already in place via validateExternalUrl. The two findings are minor: a redundant JSON.parse in batch_events that cannot cause a runtime failure, and a silent-swallow in create_insight that predates this PR but was not corrected alongside its new peer update_insight. Neither affects the correctness of the new tools or the fixes.

apps/sim/tools/posthog/create_insight.ts — silent JSON parse fallback to null is inconsistent with every other tool in this PR and could confuse users creating insights with malformed query JSON.

Important Files Changed

Filename Overview
apps/sim/tools/posthog/utils.ts New shared URL resolution helpers with SSRF guard (validateExternalUrl) for self-hosted PostHog instances; separates app API vs. ingest base URLs correctly.
apps/sim/tools/posthog/evaluate_flags.ts Migrated from undocumented /decide?v=3 to documented /flags/?v=2; added api_key body field; transformResponse correctly maps new flags object structure including variant/enabled/payload.
apps/sim/tools/posthog/batch_events.ts Added self-hosted host support, proper JSON validation in body(), and accurate events_processed count; transformResponse does a redundant second JSON.parse of params.batch to derive the count.
apps/sim/tools/posthog/delete_feature_flag.ts Correctly switched from hard DELETE (405) to soft-delete via PATCH {deleted: true}; framework now handles non-2xx before transformResponse so the always-true response is safe.
apps/sim/tools/posthog/delete_person.ts Switched to POST /persons/bulk_delete/ with {ids:[personId]} and derives success from persons_deleted count; response parsing assumption depends on undocumented response shape.
apps/sim/tools/posthog/create_insight.ts Modernised with SSRF-safe URL helpers; however query JSON parse silently falls back to null on error rather than throwing, inconsistent with new peer tool update_insight.ts.
apps/sim/tools/posthog/update_insight.ts New tool; correctly uses PATCH, validates JSON fields with throw-on-error, and parses dashboard IDs/tags from comma-separated strings.
apps/sim/tools/posthog/update_cohort.ts New tool; clean PATCH implementation with per-field JSON validation and correct soft-delete support via deleted param.
apps/sim/tools/posthog/update_experiment.ts New tool; PATCH implementation with JSON validation for parameters/filters; returns flat {experiment: data} rather than {success, output} wrapper, consistent with evaluate_flags pattern.
apps/sim/tools/posthog/create_dashboard.ts New tool for dashboard creation with optional template seeding; tag/pinned fields handled correctly.
apps/sim/tools/posthog/delete_survey.ts New DELETE tool; uses hard DELETE (surveys support it); transformResponse ignores body, relying on framework non-2xx guard for error signalling.
apps/sim/tools/error-extractors.ts Adds posthog-errors extractor for the detail+attr PostHog API error format; correctly registered in ErrorExtractorId enum.
apps/sim/blocks/blocks/posthog.ts Block UI updated for all 5 new tools and self-hosted host field; condition arrays and transformParams correctly extended for each new operation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Tool invoked] --> B{host param set?}
    B -- Yes --> C[normalizeHost]
    C --> D[validateExternalUrl / SSRF guard]
    D -- Invalid --> E[throw Error]
    D -- Valid --> F[Custom host URL]
    B -- No --> G{region?}
    G -- eu --> H[eu.posthog.com / eu.i.posthog.com]
    G -- us/default --> I[us.posthog.com / us.i.posthog.com]
    F --> J[HTTP request]
    H --> J
    I --> J
    J --> K{Non-2xx?}
    K -- Yes --> L[Framework throws before transformResponse]
    K -- No --> M[transformResponse]
    M --> N{Ingest tool? capture/batch/flags}
    N -- Yes --> O[Check data.status === 1]
    N -- No --> P[Parse API response JSON]
    O --> Q[Return success/failure]
    P --> Q
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Tool invoked] --> B{host param set?}
    B -- Yes --> C[normalizeHost]
    C --> D[validateExternalUrl / SSRF guard]
    D -- Invalid --> E[throw Error]
    D -- Valid --> F[Custom host URL]
    B -- No --> G{region?}
    G -- eu --> H[eu.posthog.com / eu.i.posthog.com]
    G -- us/default --> I[us.posthog.com / us.i.posthog.com]
    F --> J[HTTP request]
    H --> J
    I --> J
    J --> K{Non-2xx?}
    K -- Yes --> L[Framework throws before transformResponse]
    K -- No --> M[transformResponse]
    M --> N{Ingest tool? capture/batch/flags}
    N -- Yes --> O[Check data.status === 1]
    N -- No --> P[Parse API response JSON]
    O --> Q[Return success/failure]
    P --> Q
Loading

Reviews (5): Last reviewed commit: "fix(posthog): fix cross-field value leak..." | Re-trigger Greptile

Comment thread apps/sim/tools/posthog/utils.ts
Comment thread apps/sim/tools/posthog/evaluate_flags.ts Outdated
Comment thread apps/sim/tools/posthog/update_experiment.ts
…t bugs

The prior commit added if (!response.ok) branches inside transformResponse
across ~40 tool files. This is dead code — tools/index.ts already throws
on non-2xx (and error-payload) responses before transformResponse is ever
invoked, so transformResponse only ever receives already-successful
responses. Reverted to the idiomatic no-error-branch pattern used
elsewhere in the codebase (e.g. hunter/email_verifier.ts), and added a
posthog-errors errorExtractor so the framework's own error throw surfaces
PostHog's real {type, code, detail, attr} error shape instead of a
generic status message.

Verified against PostHog's live OpenAPI schema, fixed real bugs:
- delete_feature_flag: DELETE always returns 405 (hard delete not
  allowed); switched to PATCH with deleted: true
- delete_person: no single-person DELETE endpoint exists; switched to
  POST .../persons/bulk_delete/ with ids: [personId]
- create_annotation: insight_short_id is read-only on create; the
  writable field is dashboard_id
- removed nonexistent fields (experiment variants, insight
  filters/saved) not present in the current schema
- added missing trailing slashes on feature-flag/experiment URLs
The self-hosted host field accepted any string and only stripped
trailing slashes before prepending https://, so a workflow could point
it at loopback/private/link-local addresses (e.g. cloud instance-
metadata endpoints) and the executor would make a real server-side
request to it. Reuse the shared validateExternalUrl SSRF guard, same
pattern already used for Convex's custom deployment URL
(tools/convex/utils.ts); the tool executor separately re-validates with
DNS resolution and pins the resolved IP for the actual request.

Also drop an unused params arg in evaluate_flags' headers function.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/tools/posthog/batch_events.ts Outdated
Comment thread apps/sim/tools/posthog/update_cohort.ts
Comment thread apps/sim/tools/posthog/update_experiment.ts
Comment thread apps/sim/tools/posthog/update_insight.ts
…ON params

Several tools caught JSON.parse failures on user-supplied filter/query/
parameters strings and silently substituted {} or null, which would
wipe the corresponding field on the PATCH/create request instead of
surfacing an error (Cursor Bugbot flagged this for update_cohort,
update_experiment, update_insight; the same pattern existed in their
create_* / update_feature_flag / evaluate_flags counterparts, fixed for
consistency). Now throws a descriptive error, matching the existing
convention in tools/notion/query_database.ts.

Also fixes batch_events: the request body silently sent an empty batch
on invalid JSON, and transformResponse always reported "captured
successfully" even when PostHog's response indicated failure
(data.status !== 1).
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

posthog_update_experiment was the only experiment operation missing
from the region field's required-condition list; the other three
(create/get/list) already require it. Region is unconditionally
visible on this block (no condition key gates it), so this was a
required-ness inconsistency rather than a functional bug, but users
updating an EU experiment should still be prompted to select the
region explicitly rather than relying on the default.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Re: the region/update_experiment finding — clarifying and fixing. region has no condition key on this block, so it's unconditionally visible for every operation (the "required" list only controls whether it's mandatory, not whether it's shown/settable) — so EU users were never silently stuck on the US endpoint, they could always select EU manually. That said, posthog_update_experiment was inconsistently missing from the required list vs. its create/get/list siblings, so fixed in 8f023a2 for consistency.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/blocks/blocks/posthog.ts Outdated
Comment thread apps/sim/tools/posthog/delete_person.ts Outdated
Comment thread apps/sim/tools/posthog/capture_event.ts
Merges update_insight's query subblock into the same insightQuery id
already used by create_insight (Cursor flagged: it previously reused
the 'query' subblock id shared by posthog_query's HogQL field and the
cohort query fields, so switching operations could carry a stale
HogQL/cohort-JSON value into an insight PATCH). Matches the existing
merged-condition pattern used elsewhere in this block instead of
duplicating the subblock id.

Also fixes two more false-success reports in the same class as the
batch_events/delete_feature_flag fixes: capture_event now checks the
ingest response's status field (same {"status": 1} contract as
/batch/) instead of unconditionally returning success, and
delete_person now reports failure when persons_deleted is 0 instead of
always success: true.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 98d6feb. Configure here.

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

Review loop complete — Greptile 5/5, Cursor Bugbot clean, 0 unresolved threads, all on latest commit (98d6feb).

Summary of what changed across 6 commits:

  • Removed dead if (!response.ok) branches added by a first pass that misunderstood the tool executor's contract (it already throws on non-2xx before transformResponse runs)
  • Fixed real endpoint/schema bugs verified against PostHog's live OpenAPI schema: delete_feature_flag (405 on DELETE → PATCH soft-delete), delete_person (no such DELETE endpoint → bulk_delete), evaluate_flags (undocumented /decide/flags/?v=2), several nonexistent fields removed (experiment variants, insight filters/saved), annotation field rename (insight_short_iddashboard_id)
  • Added SSRF validation on the new self-hosted host field (reusing the existing validateExternalUrl guard, same pattern as tools/convex/utils.ts)
  • Fixed silent JSON-parse-failure data loss across 9 tools — invalid filters/query/parameters JSON now throws instead of silently wiping the field server-side
  • Fixed false-success reports in batch_events, capture_event, and delete_person (all now check the actual API response instead of assuming success)
  • Fixed a cross-operation value-leakage bug where update_insight's query field shared a subblock id with unrelated HogQL/cohort fields
  • Added 5 new tools for full CRUD coverage: update_insight, update_cohort, update_experiment, delete_survey, create_dashboard

Every finding across all rounds was independently verified against PostHog's live docs/OpenAPI schema or the actual framework code before fixing — 2 bot claims were disproven as false positives with doc citations, the rest were real and fixed.

@waleedlatif1 waleedlatif1 merged commit a48ecd2 into staging Jul 7, 2026
18 checks passed
@waleedlatif1 waleedlatif1 deleted the validate/posthog-integration branch July 7, 2026 17:32
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.

1 participant