usememos/memos · error
NOT expects exactly one argument
Error message
NOT expects exactly one argument
What it means
evaluateBoolExpr constant-folds boolean expressions, including CEL's NOT operator '!_'. It requires exactly one operand; a malformed AST with a different arity (only producible by hand-built or library-generated expressions, since the CEL parser itself enforces unary arity) triggers this error.
Source
Thrown at internal/filter/parser.go:502
return nil, errors.Errorf("unsupported constant %T", x)
}
}
func evaluateBool(call *exprv1.Expr_Call) (bool, bool, error) {
val, ok, err := evaluateBoolExpr(&exprv1.Expr{ExprKind: &exprv1.Expr_CallExpr{CallExpr: call}})
return val, ok, err
}
func evaluateBoolExpr(expr *exprv1.Expr) (bool, bool, error) {
if literal, err := getConstValue(expr); err == nil {
if b, ok := literal.(bool); ok {
return b, true, nil
}
return false, false, nil
}
if call := expr.GetCallExpr(); call != nil && call.Function == "!_" {
if len(call.Args) != 1 {
return false, false, errors.New("NOT expects exactly one argument")
}
val, ok, err := evaluateBoolExpr(call.Args[0])
if err != nil || !ok {
return false, false, err
}
return !val, true, nil
}
return false, false, nil
}
// evaluateNumeric constant-folds an expression to an int64 measured in seconds:
// timestamps and `now` fold to Unix epoch seconds, durations fold to a number of
// seconds, and the two combine through standard arithmetic. CEL has already
// type-checked the operand combinations, so the folded int math is well-formed.
func evaluateNumeric(expr *exprv1.Expr, now time.Time) (int64, bool, error) {
if literal, err := getConstValue(expr); err == nil {
switch v := literal.(type) {
case int64:View on GitHub (pinned to 14d757ce1f)
Solutions
- If building ASTs by hand, give '!_' exactly one argument
- Prefer passing filter strings through the normal CEL parser rather than constructing Expr protos directly
- Round-trip the AST through cel.Parse of its string form to validate structure
Example fix
// before (hand-built proto)
notCall.Args = nil
// after
notCall.Args = []*exprv1.Expr{operand} Defensive patterns
Strategy: validation
Validate before calling
// Go (AST builder): enforce unary arity when emitting NOT
if fn == "!_" && len(args) != 1 {
return errors.New("NOT needs exactly one operand")
} Prevention
- Prefer parsing filter strings over hand-building Expr protos
- Validate arity invariants after every AST rewrite pass
- Round-trip ASTs through cel.Parse to canonicalize structure
When it happens
Trigger: Programmatically constructing an exprv1.Expr_Call with Function "!_" and zero or multiple args, or a custom CEL AST builder emitting broken NOT nodes. Plain filter strings like !pinned parse correctly and never hit this.
Common situations: Code that synthesizes or transforms CEL ASTs (macros, optimizers) instead of parsing filter text; partial AST rewrites that drop operands.
Related errors
- arithmetic requires two arguments
- filter expression is empty
- filter must evaluate to a boolean value
- unsupported top-level expression
- logical AND expects two arguments
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/a04403ef0fed02d5.
Report an issue: GitHub.