| name | error-pattern-safety |
| description | Apply safe error-pattern matching rules for agentic engines. |
Error Pattern Safety Guidelines
Use these regex safety rules in agentic engines to prevent JavaScript infinite loops.
The Problem
With the JavaScript global flag (/pattern/g), zero-width matches can cause infinite loops because:
- JavaScript's
regex.exec() with the g flag uses lastIndex to track position
- When a pattern matches zero-width,
lastIndex doesn't advance
- The same position is matched repeatedly, causing an infinite loop
Dangerous Pattern Examples
❌ NEVER USE THESE PATTERNS:
/.*/g
/a*/g
/(x|y)*/g
Safe Pattern Examples
✅ ALWAYS USE PATTERNS LIKE THESE:
/error.*/gi
/error.*permission.*denied/gi
/\[(\d{4}-\d{2}-\d{2})\]\s+(ERROR):\s+(.+)/g
/access denied.*user.*not authorized/gi
Pattern Safety Rules
-
Always require at least one character match
- Use
.+ instead of .* when you need "something"
- Ensure pattern has required prefix/suffix
-
Never use bare .* as the entire pattern
- Always combine with required text:
error.*
- Never just
.* or .*?
-
Test patterns against empty string
const regex = /your-pattern/g;
if (regex.test("")) {
throw new Error("Pattern matches empty string - DANGEROUS!");
}
-
Use specific anchors when possible
- Start:
^error.*
- End:
.*error$
- Word boundaries:
\berror\b
Validation Tests
All error patterns must pass these tests:
Go Tests (pkg/workflow/engine_error_patterns_infinite_loop_test.go)
func TestPatternSafety(t *testing.T) {
pattern := "your-pattern"
regex := regexp.MustCompile(pattern)
if regex.MatchString("") {
t.Error("Pattern matches empty string!")
}
}
JavaScript Tests (pkg/workflow/js/validate_errors.test.cjs)
test("should not match empty string", () => {
const regex = new RegExp("your-pattern", "g");
expect(regex.test("")).toBe(false);
});
Safety Mechanisms in validate_errors.cjs
The validate_errors.cjs script has built-in protections:
- Zero-width detection: Checks if
regex.lastIndex stops advancing
- Iteration warning: Warns at 1000 iterations
- Hard limit: Stops at 10,000 iterations to prevent hang
if (regex.lastIndex === lastIndex) {
core.error(`Infinite loop detected! Pattern: ${pattern.pattern}`);
break;
}
Adding New Error Patterns
When adding new error patterns to engines:
-
Write the pattern with required content
{
Pattern: `(?i)error.*permission.*denied`,
LevelGroup: 0,
MessageGroup: 0,
Description: "Permission denied error",
}
-
Test against empty string
- Run:
make test-unit
- Checks:
TestAllEnginePatternsSafe
-
Test with actual log samples
- Ensure it matches real errors
- Ensure it doesn't match informational text
-
Document the pattern
- Add clear description
- Note what it's designed to catch
Pattern Conversion: Go to JavaScript
Patterns are converted from Go to JavaScript:
Pattern: `(?i)error.*permission.*denied`
new RegExp("error.*permission.*denied", "gi")
The (?i) prefix is removed because JavaScript uses the i flag instead.
Examples from Current Codebase
✅ Safe Patterns
Pattern: `(?i)error.*permission.*denied`
Pattern: `(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)\s+\[(ERROR)\]\s+(.+)`
Pattern: `(?i)access denied.*user.*not authorized`
How to Fix Unsafe Patterns
If you find a pattern that matches empty string:
Before (unsafe):
Pattern: `.*error.*`
After (safe):
Pattern: `error.*`
Pattern: `.*error.+`
Pattern: `\berror\b.*`
Testing Checklist
Before committing pattern changes:
References