Graphql recursion loop on imported incident issue type with quickactions
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Summary
GraphQL mutations that update issues (such as issueSetLabels) trigger infinite recursion and SystemStackError when the issue description contains quick action text like /severity LOW. The quick action is re-parsed and re-executed on every update cycle, causing a stack overflow.
Steps to reproduce
- Create a new incident (or issue with incident type) by importing from bugzilla
- Edit the incident description to include the literal text
/severity LOWanywhere in the description body (not executed as a quick action, just as plain text - for example, as part of imported content or documentation) - Save the incident
- Attempt to update labels on the incident via the UI (add or remove any label)
- Observe HTTP 500 error in the UI with message "An error occurred while updating labels"
- Check
production.logforSystemStackError: stack level too deep
Example Project
Support Ticket: https://gitlab-federal-support.zendesk.com/agent/tickets/14105 (US citizenship required)
What is the current bug behavior?
When an issue/incident description contains quick action syntax (e.g., /severity LOW), any subsequent update to the issue via GraphQL mutations triggers the Issues::UpdateService, which parses the description and executes the quick action. This quick action execution calls Issues::UpdateService again, which re-parses the description, finds the same quick action, executes it again, and so on - creating an infinite recursion loop until the Ruby stack is exhausted.
The result is:
- HTTP 500 response to the client
-
SystemStackError: stack level too deepin logs - ~190MB memory allocation per failed request
- ~1.9 second response time before failure
- The issue becomes effectively uneditable via the UI for any attribute (labels, assignees, etc.)
What is the expected correct behavior?
Quick actions in issue descriptions should not be re-executed when performing unrelated updates to the issue. The expected behaviors would be:
- Quick actions should only be executed once when initially entered (either via description save or comment), not on every subsequent update
- OR the update service should detect that it's being called recursively and skip quick action parsing
- OR quick action parsing should be skipped when the description hasn't changed
Updating labels on an issue should succeed regardless of what text is present in the description.
Relevant logs and/or screenshots
Exception from production.log:
{
"method": "POST",
"path": "/gitlab/api/graphql",
"status": 500,
"correlation_id": "01KBK27YYSXBQY0NPN6JN6E83T",
"exception.class": "SystemStackError",
"exception.message": "stack level too deep",
"exception.backtrace": [
"activerecord (7.0.8.7) lib/active_record/associations/preloader/branch.rb:121:in `flat_map'",
"activerecord (7.0.8.7) lib/active_record/associations/preloader/branch.rb:121:in `build_children'",
"activerecord (7.0.8.7) lib/active_record/associations/preloader/branch.rb:17:in `initialize'",
"activerecord (7.0.8.7) lib/active_record/associations/preloader/branch.rb:123:in `new'",
"activerecord (7.0.8.7) lib/active_record/associations/preloader/branch.rb:123:in `block (2 levels) in build_children'",
"..."
],
"duration_s": 1.85364,
"mem_bytes": 190103646,
"mem_objects": 1020483
}
GraphQL operation details:
{
"operation_name": "issueSetLabels",
"query_fingerprint": "issueSetLabels/TndOuQaN8QWMz63U7AM1nEkVwW1X-u8EFL6Gplmn3DU=/1/...",
"is_mutation": true,
"variables": "{\"input\"=>{\"iid\"=>\"8635\", \"projectPath\"=>\"group/project\", \"removeLabelIds\"=>[\"gid://gitlab/GroupLabel/4376\"]}}"
}
Recursion pattern visible in full backtrace:
The backtrace shows repeated cycles through:
Issues::UpdateService#executeQuickActions::InterpretService#execute-
Issues::UpdateService#execute(recursive) - (repeating hundreds of times until stack exhausts)
Output of checks
Results of GitLab environment info
Expand for output related to GitLab environment info
GitLab version: 17.11.7 GitLab edition: Enterprise Edition Deployment type: Omnibus Database adapter: PostgreSQL Database version: 14.x Ruby version: 3.2.x (bundled with Omnibus) Rails version: 7.0.8.7
Results of GitLab application Check
Expand for output related to the GitLab application check
All GitLab application checks pass. This is not a configuration issue - it is a code-level bug in the update service recursion handling.
Possible fixes
The issue likely originates in app/services/issues/update_service.rb and its interaction with app/services/quick_actions/interpret_service.rb.
Potential fixes:
-
Skip quick action parsing when description hasn't changed: In
Issues::UpdateService, only call the quick action interpreter whenparams[:description]is present and different from the current description. -
Add recursion guard: Add a flag or context variable that prevents re-entry into quick action parsing during an update cycle:
# Pseudocode def execute return if @parsing_quick_actions @parsing_quick_actions = true # ... existing logic ensure @parsing_quick_actions = false end -
Track executed quick actions: Mark quick actions as "consumed" after first execution so they aren't re-executed on subsequent updates. This could be done by escaping or removing the quick action syntax after execution.
-
Scope quick action parsing to specific update paths: Only parse quick actions when the update originates from a description change or comment, not from label/assignee/milestone updates.
Related code paths to investigate:
app/services/issues/update_service.rbapp/services/quick_actions/interpret_service.rbapp/services/issuable_base_service.rb-
ee/app/services/ee/issues/update_service.rb(if EE-specific logic is involved)
Patch release information for backports
This bug affects the usability of issues/incidents that contain quick action syntax in their descriptions. It was discovered when importing issues from Bugzilla where severity fields were converted to /severity text in descriptions.
Impact assessment:
- Issues become uneditable once they contain quick action text in description
- Affects all GraphQL mutations that trigger
Issues::UpdateService - No data loss, but significant UX impact
- Workaround exists (manually remove quick action text via Rails console using
update_column)