Skip to main content
When your CEL expression doesn’t work, this guide helps you figure out why. Most problems fall into two categories: errors caught when you save (easy to fix) and silent failures at runtime (harder to debug).

Error types

TypeWhen caughtExample
Compile-timeWhen you save the expressionSyntax errors, type mismatches, undefined variables
RuntimeWhen the expression evaluatesEmpty lists, missing users, function failures
Compile-time errors are caught immediately and prevent saving. Runtime errors are more subtle - your expression saves fine but behaves unexpectedly when it runs.

Common compile-time errors

Syntax errors

Error: Syntax error: mismatched input
// BAD: Missing closing parenthesis
subject.department == "Engineering"

// BAD: Wrong quote type - single quotes not valid in CEL
subject.department == 'Engineering'

// GOOD:
subject.department == "Engineering"

Undefined variable

Error: undeclared reference to 'xyz'
// BAD: Typo in variable name
subjct.department == "Engineering"

// BAD: Variable not available in this environment
ctx.trigger.user_id  // ctx only available in workflows and automations

// GOOD:
subject.department == "Engineering"
Check the expressions reference to see which variables are available in each context.

Type mismatch

Error: found no matching overload
// BAD: Comparing string to number
subject.profile["level"] > 5  // level might be stored as a string

// GOOD: Convert to same type
int(subject.profile["level"]) > 5
subject.profile["level"] == "5"

Wrong return type

Error: expected type 'bool' but found 'User'
// BAD: Policy condition must return true/false, not a User
c1.directory.users.v1.FindByEmail("alice@company.com")

// GOOD: Return a boolean by adding a comparison
c1.directory.users.v1.FindByEmail("alice@company.com").department == "Engineering"
Required return types by context:
ContextMust return
Policy conditionstrue or false
Dynamic groupstrue or false
Policy step approversOne or more users
Access review filterstrue or false
Automation triggerstrue or false
Account provisioningText value

Common runtime issues

These problems don’t show errors when you save - they only appear when the expression runs against real data.

Empty list causes step skip

Symptom: Policy step is skipped unexpectedly. Cause: Approver expression returned an empty list [].
// DANGER: Returns [] if user has no manager -> step is SKIPPED, not failed
c1.directory.users.v1.GetManagers(subject)
Solution: Add a fallback approver:
size(c1.directory.users.v1.GetManagers(subject)) > 0
  ? c1.directory.users.v1.GetManagers(subject)
  : appOwners
This is the most common source of unexpected behavior in policy expressions. An empty approver list doesn’t fail - it silently skips the step entirely.

Index out of bounds

Symptom: Expression fails with index error. Cause: Accessing [0] on an empty list.
// BAD: Fails if no managers exist
c1.directory.users.v1.GetManagers(subject)[0]

// GOOD: Check size first
size(c1.directory.users.v1.GetManagers(subject)) > 0
  ? [c1.directory.users.v1.GetManagers(subject)[0]]
  : appOwners

User not found

Symptom: FindByEmail or GetByID fails. Cause: The user doesn’t exist in your directory, or the email/ID is wrong.
// DANGER: Fails if email doesn't exist
c1.directory.users.v1.FindByEmail("nonexistent@company.com")
Solutions:
  1. Verify the user/email exists before deploying
  2. Use entitlement-based approvers instead of hardcoded emails
  3. For optional lookups, use conditional logic

Empty string comparisons

Symptom: Expression returns false when you expect true. Cause: The field is empty, so it doesn’t match your expected value.
// Returns false if department is empty (might be intentional, might not)
subject.department == "Engineering"

// Explicit check for whether the field has a value
has(subject.department) && subject.department == "Engineering"
has() checks if a field exists, not if it has a non-empty value. A field can exist with an empty string "", and has() will return true.

Profile key missing

Symptom: Expression fails when accessing profile data. Cause: The profile key doesn’t exist for this user.
// BAD: Fails if costCenter doesn't exist in profile
subject.profile["costCenter"] == "CC-123"

// GOOD: Check first using has()
has(subject.profile.costCenter) && subject.profile["costCenter"] == "CC-123"

// ALTERNATIVE: Use "in" operator
"costCenter" in subject.profile && subject.profile["costCenter"] == "CC-123"

Profile key has spaces

Symptom: Syntax error or unexpected behavior. Cause: Dot notation doesn’t work with spaces in key names.
// BAD: Dot notation fails with spaces
subject.profile.Cost Center

// GOOD: Use bracket notation with quotes
"Cost Center" in subject.profile && subject.profile["Cost Center"] == "R&D"

Debugging strategies

1. Check return type first

Before deploying, verify your expression returns the correct type:
If you’re writing…Expression must return…
Policy conditiontrue or false
Dynamic group membershiptrue or false
Policy step approversOne or more users

2. Preview dynamic groups

Before saving a dynamic group expression:
  1. Use the preview feature to see which users would be included
  2. Check for both false positives (included but shouldn’t be) and false negatives (excluded but shouldn’t be)
  3. Verify the group isn’t empty or doesn’t include everyone

3. Test with specific users

When debugging:
  1. Pick a user who should match and one who shouldn’t
  2. Mentally evaluate the expression against both
  3. Check intermediate values if using compound expressions

4. Simplify and isolate

For complex expressions, break them apart:
// Hard to debug
(a && b) || (c && !d && e)

// Easier: Test each part separately
a  // Does this work?
b  // Does this work?
a && b  // Now combine

Error messages reference

Compile-time errors

Error messageMeaningFix
Syntax error: mismatched inputBrackets, quotes, or operators are wrongCheck syntax carefully
undeclared reference to 'x'Variable doesn’t exist or is misspelledCheck spelling, check context
found no matching overload for 'func'Wrong argument types for functionCheck function signature
expected type 'X' but found 'Y'Return type doesn’t match what’s neededMatch the required return type

Runtime behaviors

BehaviorCauseSolution
Step skippedApprover expression returned []Add fallback approvers
Always falseField is empty or missingUse has() to check first
Index errorAccessing element in empty listCheck size() > 0 first
Function errorUser or resource not foundVerify IDs/emails before deploying

Context-specific issues

ContextCommon issueSolution
Automation triggersUsing wrong object typeCheck if trigger is for users or accounts
Automation triggersNested nil objectsCheck parent exists with has()
Access reviewsUsing wrong scopeKnow if you’re in User scope or Account scope
Dynamic groupsExpression always trueEveryone gets included - narrow the filter
WorkflowsReference undefined stepCheck step order and naming
WorkflowsTemplate syntax errorCheck matching {{ }} braces
Account provisioningOptional variable not providedCheck if entitlement/task are available in context

Best practices

Start simple

// Start with the simplest version
subject.department == "Engineering"

// Add complexity only as needed
subject.department == "Engineering" && subject.jobTitle.contains("Manager")

Always use fallbacks for approvers

// Always have a fallback so steps don't get skipped
size(managers) > 0 ? managers : appOwners

Avoid hardcoded users

// BAD: Breaks when user leaves company
[c1.directory.users.v1.FindByEmail("john@company.com")]

// GOOD: Uses a managed entitlement that can be updated
c1.directory.apps.v1.GetEntitlementMembers("approvers-app", "security-team")

Document complex expressions

If your expression is complex enough to need debugging, consider:
  • Breaking it into multiple policy rules
  • Adding comments (CEL supports // comments)
  • Using multiple policy steps instead of complex approver logic

Getting help

  1. Check this guide - Most issues are covered above
  2. Check the reference - Variable availability varies by context
  3. Preview first - Always preview dynamic groups before saving
  4. Test incrementally - Build complex expressions piece by piece