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
Nobody has claimed this yet.
- 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 (hasInvalidRegexFilter → validateQuery, 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 427CANNOT_COMPILE_REGEXPis registered withoutuser_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
- Data management → Actions → New action → Pageview → URL: pick matches regex and enter
/shardlibrary/\d+/\(note the trailing backslash) → name it → Save. - 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". - Create a Trends insight with that action as the series, date range last 30 days.
- Observe:
POST /api/environments/:id/query/TrendsQuery/→ 500ClickHouse error while executing query.; the insight shows "There was a problem completing this query". - Same happens for
SELECT count() FROM events WHERE matchesAction('<name>')in the SQL editor and for an EventsQuery withactionIdover a range that contains$pageviewrows. - Control: on a
$pageviewtrends series add the property filter$current_urlmatches regex/shardlibrary/\d+/\. The query is blocked client-side; via the API it returns 400Invalid 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:
ActionStepJSONSerializer.validate():re2.compileonurl/href/textwhen the corresponding*_matching == "regex", raiseValidationError({"url": "Invalid regular expression: …"}). Stops new broken actions at save.steps_to_expr: call_validate_regex()before building eachRegexcompare op. Turns existing broken actions into the same 400Invalid regular expressionthat property filters get, everywhere the action is used, and independent of whether ClickHouse ever evaluates the pattern.- Frontend: reuse
isValidRE2in 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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