netbirdio/netbird · error
unclosed parenthesis
Error message
unclosed parenthesis
What it means
Parse error from the recursive-descent parser of NetBird's capture filter language (util/capture/filter.go). After consuming '(' and successfully parsing the inner or-expression, parser.expect(")") fails because the token stream ended or diverged, so parseUnary returns 'unclosed parenthesis'.
Source
Thrown at util/capture/filter.go:374
}
func (p *parser) parseUnary() (exprNode, error) {
switch p.peek() {
case "not":
p.next()
inner, err := p.parseUnary()
if err != nil {
return nil, err
}
return nodeNot(inner), nil
case "(":
p.next()
inner, err := p.parseOr()
if err != nil {
return nil, err
}
if err := p.expect(")"); err != nil {
return nil, fmt.Errorf("unclosed parenthesis")
}
return inner, nil
default:
return p.parseAtom()
}
}
func (p *parser) parseAtom() (exprNode, error) {
tok := p.next()
if tok == "" {
return nil, fmt.Errorf("unexpected end of expression")
}
switch tok {
case "host":
addr, err := p.parseAddr()
if err != nil {
return nil, fmt.Errorf("host: %w", err)View on GitHub (pinned to 93e97f4bf1)
Solutions
- Add the missing ')' so every '(' is closed; use editor bracket matching or a paren-count lint on the string
- Build nested groups programmatically and close each group in the same scope that opened it
- Compile the filter once at config-load time (before starting the capture) so the error surfaces with full context
Example fix
// before filter := "host 10.0.0.1 and (port 443 or port 8443" // after filter := "host 10.0.0.1 and (port 443 or port 8443)"
Defensive patterns
Strategy: validation
Validate before calling
func balancedParens(expr string) bool {
depth := 0
for _, r := range expr {
switch r {
case '(':
depth++
case ')':
depth--
if depth < 0 {
return false
}
}
}
return depth == 0
}
if !balancedParens(filter) {
return fmt.Errorf("filter has unbalanced parentheses")
} Type guard
func isValidFilterExpr(s string) bool {
return strings.TrimSpace(s) != "" && balancedParens(s)
} Try / catch
Parse the filter at config-load time and wrap the parse error with the offending expression; show the string with a caret at the position of the last unmatched '('. Prevention
- Validate the expression before starting a capture session, not after
- Close each '(' in the same code block that opened it when composing filters programmatically
- Lint filter strings with a paren-balance check in config validation
When it happens
Trigger: A filter with more '(' than ')': 'host 10.0.0.1 and (port 443', '((tcp or udp)', or nested groups like 'not (tcp and (port 53' where a final ')' was never typed.
Common situations: Hand-written packet-capture filter strings; programmatically composed filters where a conditional clause appends '(' without a matching ')'; long one-liners pasted from issues or notes.
Related errors
- parse filter: %w
- invalid filter: %w
- create capture session: %w
- management client is not initialised
- profile not found
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/cfd877d4bff0510e.
Report an issue: GitHub.