PostHog / PostHog/posthog

fix(actions): action step regexes are never validated, so a bad pattern saves fine and every query using the action fails with a generic 500

Open
#96,347 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug team/product-analytics
Dominant language
Python
Stars
39.9k
Forks
3.4k
Avg merge
6h 51m
Merged PRs (30d)
232

Description

Bug Description

An action match group can carry a regular expression in three places: URL (url_matching: regex), link href (href_matching: regex) and element text (text_matching: regex). None of them is validated, on the client, at save time, or at query time. A pattern ClickHouse's RE2 cannot compile (for example a trailing backslash, or a PCRE lookahead) is accepted with HTTP 201 and no warning. From then on every surface that resolves the action, trends/funnels, matchesAction() in SQL, cohorts, and the action page's own "Matching events" preview, fails with:

{"type":"server_error","code":"error","detail":"ClickHouse error while executing query."}

and the insight UI shows "There was a problem completing this query. Try again in a moment. If the problem continues, contact support." Nothing points at the action, let alone at the regex. The action itself keeps looking healthy in Data management → Actions.

The same pattern entered as a property filter is handled correctly on both ends: the frontend refuses to run the query (hasInvalidRegexFiltervalidateQuery, frontend/src/scenes/insights/utils/queryUtils.ts) and the API returns a 400:

{"type":"validation_error","code":"hogql_query_error","detail":"Invalid regular expression: '/shardlibrary/\\d+/\\'"}

That asymmetry is the bug. #58706 added _validate_regex (posthog/hogql/property.py:580-590) for regex/not_regex property filters, explicitly citing a trailing backslash as a real-world example, but the action step path in steps_to_expr (posthog/hogql/property.py:1638 href, :1664 text, :1699 url) builds the Regex compare op directly and never calls it. ActionStepJSONSerializer (products/actions/backend/api/action.py:70-140) has no regex check either, and refresh_bytecode (products/actions/backend/models/action.py:132-146) does not compile the pattern, so bytecode_error stays null. On the frontend the URL/href/text inputs in products/actions/frontend/components/ActionStep.tsx only show an "RE2 syntax applies." link; isValidRE2 from frontend/src/lib/utils/regexp.ts is not used there.

Two things make this worse than a plain 500:

  • ClickHouse compiles the regex lazily, so a query whose time window has no candidate rows returns 200 with zero results instead of an error. The action page's "Matching events" panel defaults to "Last 24 hours", so on a quiet project it shows "There are no matching events for this query" for an action that can never run. Widening the range flips it to the generic error.
  • The error is wrapped as InternalCHQueryError (CH code 427 CANNOT_COMPILE_REGEXP is registered without user_safe, posthog/errors.py:728), so it is also reported as an internal exception rather than user input.

Underlying ClickHouse error (26.6.2):

Code: 427. DB::Exception: OptimizedRegularExpression: cannot compile re2: /shardlibrary/\d+/\, error: trailing \. (CANNOT_COMPILE_REGEXP)

How to reproduce

  1. Data management → Actions → New actionPageview → URL: pick matches regex and enter /shardlibrary/\d+/\ (note the trailing backslash) → name it → Save.
  2. Observe: saved with no error (POST /api/projects/:id/actions/ → 201). The "Matching events" panel at its default "Last 24 hours" shows "There are no matching events for this query".
  3. Create a Trends insight with that action as the series, date range last 30 days.
  4. Observe: POST /api/environments/:id/query/TrendsQuery/ → 500 ClickHouse error while executing query.; the insight shows "There was a problem completing this query".
  5. Same happens for SELECT count() FROM events WHERE matchesAction('<name>') in the SQL editor and for an EventsQuery with actionId over a range that contains $pageview rows.
  6. Control: on a $pageview trends series add the property filter $current_url matches regex /shardlibrary/\d+/\. The query is blocked client-side; via the API it returns 400 Invalid regular expression: ….

Reproduced on master @ 3697dc49e (local, ClickHouse 26.6.2). API-level repro without the UI:

POST /api/projects/:id/actions/
{"name":"bad regex","steps":[{"event":"$pageview","url":"/shardlibrary/\\d+/\\","url_matching":"regex"}]}   → 201

POST /api/environments/:id/query/
{"query":{"kind":"TrendsQuery","series":[{"kind":"ActionsNode","id":<id>}],"dateRange":{"date_from":"-30d"}}}   → 500

Additional context

  • Prior art: #58706 (merged 2026-05-26) and #93586 (merged 2026-09-02) validate property-filter regexes only. #55528 (closed unmerged) proposed marking CH 427 as user-safe, which would at least fix the message for every surface; #78096 (closed) did the same for path cleaning. No issue or PR covers action steps.
  • Not a duplicate of #95863 (draft), which tags actions whose event went stale or was deleted. This issue is about a match condition that can never compile, and that PR does not touch validation.
  • Suggested fix, all three parts small:
    1. ActionStepJSONSerializer.validate(): re2.compile on url / href / text when the corresponding *_matching == "regex", raise ValidationError({"url": "Invalid regular expression: …"}). Stops new broken actions at save.
    2. steps_to_expr: call _validate_regex() before building each Regex compare op. Turns existing broken actions into the same 400 Invalid regular expression that property filters get, everywhere the action is used, and independent of whether ClickHouse ever evaluates the pattern.
    3. Frontend: reuse isValidRE2 in the action step URL/href/text inputs for an inline error, matching the property-filter UX.

Debug info

  • PostHog Cloud, region and project ID: EU, project 14109 (customer); reproduced on local master @ 3697dc49e
  • PostHog Hobby self-hosted with docker compose, version/commit: n/a
  • PostHog self-hosted with Kubernetes (deprecated), version/commit: n/a

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ActionStepJSONSerializer in products/actions/backend/api/action.py, steps_to_expr in posthog/hogql/property.py, and the URL, href, and text inputs in products/actions/frontend/components/ActionStep.tsx. Compare these paths with _validate_regex and isValidRE2, then reproduce the supplied bad-pattern API request. Done means invalid action regexes are surfaced as validation errors at save or query time and the frontend reports the same problem before submission.

Written by the indexing model from the issue text.

Assessment

Tech stack
clickhouse, python, typescript
Domain
api, backend, databases, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.