Marking a resolver-generated GraphQL argument as experiment causes unbounded recursion at query time
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Problem
Adding experiment: { milestone: '19.5' } to the 26 generated <name>Not arguments introduced in !256398 (merged) made every one of them fail at execution time with SystemStackError (stack level too deep), across all 8 aggregation engines. The positive filters, which carry no experiment marker, are unaffected. As a result those exclusion arguments ship unmarked in the originating MR, meaning they land as committed API in 19.5 with no experiment off-ramp.
Evidence
Reproduced deterministically outside any spec harness:
# returns normally
GitlabSchema.execute('query { group(fullPath: "g") { analytics { duoWorkflows(status: ["finished"]) { aggregated { nodes { totalCount } } } } } }')
# raises SystemStackError
GitlabSchema.execute('query { group(fullPath: "g") { analytics { duoWorkflows(statusNot: ["finished"]) { aggregated { nodes { totalCount } } } } } }')- Bisected to the marker itself: disabling the single line in
Gitlab::Database::Aggregation::Graphql::Adapterthat forwardsexperiment:onto the generated argument makes every exclusion argument work; restoring the line breaks them again. - Mechanism, as far as it has been traced:
Gitlab::Graphql::Deprecations#init_gitlab_deprecationturns anexperiment:marker into adeprecation_reason.Gitlab::Graphql::Tracers::InstrumentationTracer#execute_multiplexhas anensureblock that callsexport_query_info, thenerror_type, thenquery.result. When execution raises before the result is memoized,query.resultre-enters execution, which runs the tracer again, and so on until the stack is exhausted. The recursion is in the tracer, not in the aggregation code. - Ruled out: adding explicit
validates: { length: { maximum: ... } }to the arguments, to test whether the automatic array validation added byTypes::BaseArgumentwas interacting with the deprecation. It made no difference. - Not yet established: whether this affects any experiment-marked argument, or only arguments generated dynamically on a resolver via
argument(name, type, **kwargs). There are many existing experiment-marked schema items that work, so the dynamic-generation path is the suspected difference, but that has not been confirmed. - Impact in CI: this did not surface as a fast failure. The job
rspec-ee integration pg17 clickhouse25froze with no output for roughly 85 minutes and was killed by the 1h30m job timeout, twice, on two different pipelines and runners. The recursion logs and increments metrics at each level, so it thrashes rather than overflowing quickly, which makes it easy to misread as an infrastructure failure.
Proposed direction
Confirm the scope of the problem, then decide whether the fix belongs in the tracer (not re-entering query.result from its own ensure) or in how experiment markers are applied to resolver-generated arguments.