Portal Community

1. No Wildcard in Field Value

If the field string contains no wildcard syntax, it short-circuits immediately at the orchestrator level — no parsing, no directive call.

// Input
"hello world"

// Result: EvaluationResponse.ShortCircuit("hello world")
// IsSuccess = true, Value = "hello world", FormattedValue = "hello world"
// DurationMs ≈ 0
Zero Cost Fields without expressions pay no evaluation overhead.

2. Path Not Found / Null Value

When a dot-path does not exist in the data source:

// {@ $var.missingVariable }
// Memory does not contain "missingVariable"

// Without @default: → Failure
// ErrorCode = PathNotFound, ErrorMessage = "Variable 'missingVariable' not found"

// With @default:0 → Success
// {@ $var.missingVariable @default:0 }
// Value = "0"
Directive responsibility Not all directives treat missing values identically. $ctx.env.MISSING returns PathNotFound. $input.current.missingField may return null or PathNotFound depending on the item structure. Always use @default when a path may legitimately be absent.

3. Depth Limit Exceeded (Depth ≥ 10)

Every time a directive chains to another directive or expression, Depth increments. At depth = 10, the chain is hard-stopped.

// Scenario: @aliasA → {@ @aliasB } → {@ @aliasC } → ... → depth 10
// At depth 10: GuardChainDepth() throws → converted to:
// EvaluationResponse.Failure(EvaluationErrorCode.DepthLimitExceeded, ...)
Always call GuardChainDepth() before chaining Every directive that chains MUST call GuardChainDepth() before invoking ChainExpressionAsync or ChainToDirectiveAsync. Failure to do so can allow runaway recursion until a stack overflow.

4. Cycle Detection via VisitedKeys

The VisitedKeys set on the EvaluationRequest tracks which expression keys have already been evaluated in this chain. If a key appears twice, it is a cycle.

// Scenario: Canned expression @loop contains "{@ @loop }"
// First evaluation: VisitedKeys = {"loop"}
// ChainExpressionAsync("{@ @loop }") → adds "loop" → already in set
// → EvaluationResponse.Failure(EvaluationErrorCode.CycleDetected)
VisitedKeys is immutable on chain request.Chain() and request.ForSubDirective() return a NEW request with the key added to an immutable copy of VisitedKeys. The parent request's set is never mutated.

5. Isolation Level Violation

Using $js, $cs, or $api when the context's IsolationLevel is too low produces a hard failure — not a silent null.

// Context.IsolationLevel = Safe
// Expression: {@ $js`return 42` }

// → Failure(IsolationViolation,
//     "$js requires Sandboxed isolation level or higher")

The node executor or workflow engine is responsible for setting the correct isolation level in EvaluationContext based on the node type's declared requirements.

6. Unknown Directive Name

If the parser extracts a directive name that is not registered in DI:

  1. Orchestrator checks for IDefaultExpressionDirectiveService
  2. If a default is registered → routes there
  3. If no default → Failure(DirectiveNotFound)
// No $weather directive registered
// {@ $weather.temperature }
// → routes to default directive (e.g., $var)
// → tries to resolve "weather.temperature" as a memory variable
// → likely Failure(PathNotFound)

7. Malformed Expression Syntax

// Unclosed brace
"{@ $ctx.user.name"    // no closing }
// → CanParse = false → treated as plain string (no evaluation)

// Empty directive
"{@  }"
// → ParseError

// Missing dollar sign (AtBrace parser only)
"{@ ctx.user.name }"
// → ParseError (no $-prefixed directive token found)

8. Template with a Failing Sub-expression

In a template string, if one region fails, the orchestrator does NOT fail the entire template. The failed region is substituted with an empty string (or the error message in trace mode).

// Template: "Hello {@ $ctx.user.name }, order {@ $var.missingId } done."
// $ctx.user.name → "Alice"
// $var.missingId → Failure(PathNotFound)

// Result: "Hello Alice, order  done."
// (missing ID substituted with empty string)
// EnableExpressionTrace=true → "Hello Alice, order [PathNotFound:missingId] done."

9. Binary Field Access

Binary fields (file attachments, images) in node input have a special sub-path prefix:

// Accessing binary data
"{@ $input.current._binary.attachment }"
// Returns binary descriptor (filename, mimeType, data reference)
// NOT the raw bytes inline — use @base64 option to encode

"{@ $input.current._binary.profileImage @base64 }"
// Returns Base64-encoded image data

10. Concurrent Node Execution

EvaluationContext should NOT be shared across concurrent node executions. Each parallel branch in a workflow must get its own context (different NodeKey, different IExpressionCache scope). The context's cache is not thread-safe across different NodeKey values.

// ✅ Correct: each parallel node gets its own context
var ctx1 = BuildContext(node1, executionState);
var ctx2 = BuildContext(node2, executionState);

// ❌ Wrong: sharing context across parallel nodes
var sharedCtx = BuildContext(...);
await Task.WhenAll(
    orchestrator.EvaluateAsync(field1, sharedCtx, ct),  // race condition on cache
    orchestrator.EvaluateAsync(field2, sharedCtx, ct)
);

11. Cache Miss After Scope Invalidation

Cache entries at Node scope are invalidated when a node finishes. If a directive accidentally stores results in a wider scope (Thread or Process) without being told to, stale data may persist.

Rule: Never write to the cache manually inside a directive. Let the orchestrator handle cache storage based on the ICacheMarkerOption in the expression. Directives only READ from the cache.

12. $api Domain Not Allowlisted

// {@ $api.MyService/data.field }
// "MyService" resolves to https://api.untrusted-external.com
// Not in ExpressionEvaluation:Api:AllowedDomains

// → Failure(DomainNotAllowed,
//     "Domain 'api.untrusted-external.com' is not in the allowed list")

Error Code Quick Reference

SituationErrorCodeRecoverable?
Path doesn't exist in dataPathNotFoundYes — use @default
Directive name not registeredDirectiveNotFoundNo — register the directive
Chain depth ≥ 10DepthLimitExceededNo — fix circular expressions
Same key twice in chainCycleDetectedNo — fix circular aliases
Wrong isolation levelIsolationViolationYes — raise isolation level
Script throwsScriptErrorYes — fix script logic
Required field is nullValidationErrorYes — ensure data is present
HTTP call failedExternalApiErrorYes — retry or fix endpoint
Target domain blockedDomainNotAllowedYes — add to allowlist
Template render errorTemplateRenderErrorYes — fix Liquid syntax
Alias not in DBCannedExpressionNotFoundYes — create the alias
Directive timed outTimeoutYes — optimize or increase limit