Fix filter evaluator rejecting delegated hash payloads

What does this MR do and why?

Filter rules on the pipeline_hooks event never match, so AI flow triggers and project webhooks with custom filters on pipeline events are broken, including the fix_pipeline/v1 foundational flow. This fixes the guard in Gitlab::FilterEvaluator.dig_value so it accepts a SimpleDelegator around a Hash, not just a plain Hash.

Root cause and blast radius

Commit ce9701f8 (merged 2026-09-15) added a guard, unless value.is_a?(Hash), to lib/gitlab/filter_evaluator.rb:54, so a missing path segment fails only that rule instead of raising. Before that commit the reduce called value['object_attributes'] directly.

The pipeline hook payload, built by Gitlab::DataBuilder::Pipeline.build(pipeline) at app/services/ci/pipelines/hook_service.rb:30, is a SimpleDelegator wrapping the payload hash (lib/gitlab/data_builder/pipeline.rb:7). Delegator does not forward is_a?, so the guard fails on the very first path segment, object_attributes, the resolved value becomes nil, and every eq or in rule is unsatisfiable while ne and not_in match everything.

This is shared code, so it breaks both AI flow triggers (ee/app/services/ai/flow_triggers/filter_evaluator.rb:16) and webhook custom filters (app/models/projects/triggered_hooks.rb:21). Integrations::ExecuteWorker was never affected, because its data arrives Sidekiq-serialized as a plain Hash.

The fix changes the guard to value.respond_to?(:key?), which is true for a Hash and for a delegator around a Hash, and false for String, nil, Integer, Array, and a delegator around a String or nil, keeping the original guard's intent with no change for other callers.

Existing specs missed this because every spec on the chain fed a plain Hash: ee/spec/services/ee/ci/pipelines/hook_service_spec.rb:18 stubs the data builder to {}, ee/spec/services/ai/flow_triggers/event_trigger_service_spec.rb stubs Gitlab::FilterEvaluator.evaluate, and spec/lib/gitlab/filter_evaluator_spec.rb had no non-Hash top-level data case. Two new specs, written first and confirmed to fail with "expected true, got false" before the fix, now cover this: a SimpleDelegator payload case in spec/lib/gitlab/filter_evaluator_spec.rb, and a case in ee/spec/services/ai/flow_triggers/filter_evaluator_spec.rb that evaluates the real Gitlab::DataBuilder::Pipeline payload of a failed pipeline against an object_attributes.status in ["failed"] filter.

Out of scope: lib/gitlab/filter_evaluator.rb:65 also has value[key] || value[key.to_sym], which treats a legitimate false as missing, so object_attributes.default_branch eq false can never match. This is pre-existing and not addressed here.

References

Screenshots or screen recordings

Not applicable: backend-only change with no UI impact.

How to set up and validate locally

  1. Create an AI flow trigger on a project for the pipeline_hooks event with the filter object_attributes.status in ["failed"] (the default the UI writes when you pick "Failed" under "Run when").

  2. In a Rails console, evaluate the filter against a real failed pipeline payload:

    pipeline = Ci::Pipeline.failed.last
    filter = { 'rules' => [{ 'field' => 'object_attributes.status', 'operator' => 'in', 'value' => ['failed'] }] }
    Gitlab::FilterEvaluator.evaluate(filter, Gitlab::DataBuilder::Pipeline.build(pipeline))

    On master this returns false. With this MR it returns true.

  3. Fail a pipeline in the project and confirm a flow session (Ai::DuoWorkflows::Workflow) is created for the trigger.

Test plan, all passing locally:

  • spec/lib/gitlab/filter_evaluator_spec.rb (17 examples)
  • ee/spec/services/ai/flow_triggers/filter_evaluator_spec.rb, ee/spec/services/ee/ci/pipelines/hook_service_spec.rb, ee/spec/services/ai/flow_triggers/event_trigger_service_spec.rb, spec/models/projects/triggered_hooks_spec.rb (52 examples combined)
  • RuboCop on the three changed files, no offenses

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

🤖 Generated with Claude Code

Merge request reports

Loading
Loading