Sign chat agent requests from the chat feature setting

What does this MR do and why?

Reported in #621674 (closed) and explained in comments #621674 (comment 3726614031) and #621674 (comment 3802588426). Duo review responds to comments using the classic chat feature, but their auth is based of Agentic chat.

This means that GitLab Duo self-hosted Duo Enterprise instances with offline licenses users cannot @ mention GitLabDuo during MR reviews.

This MR passes the feature setting when creating the request headers so they are generated with the proper context.

References

#621674 (closed)

How to set up and validate locally

Environment set up

Prerequisites:
  1. AIGW set up locally
  2. gitlab runner

Set up steps

  1. GDK in self-managed mode

In gdk.yml:

env: GITLAB_SIMULATE_SAAS: "0"

Make sure CLOUD_CONNECTOR_SELF_SIGN_TOKENS appears in neither gdk.yml env: nor env.runit.

  1. AI Gateway with real auth

In gitlab-ai-gateway/.env:

  • AIGW_CUSTOM_MODELS__ENABLED=true
  • AIGW_GITLAB_URL=http://:3000
  • AIGW_AUTH__BYPASS_EXTERNAL — unset or false
  • AIGW_AUTH__BYPASS_JWT_SIGNATURE — unset/false
  • AIGW_MOCK_MODEL_RESPONSES=true # if you do not have a self hosted model set up.
  • AIGW_LOGGING__ENABLE_REQUEST_LOGGING=true — needed for the auth_error_details / token_issuer lines

gdk restart

  1. License + add-ons (Duo Enterprise, no Agent Platform)

In gitlab

DEVELOPMENT_AI_GATEWAY_URL=http://localhost:5052 bundle exec rake "gitlab:duo:setup[duo_enterprise]" bundle exec rake gitlab:duo:seed_self_hosted_models

  1. Feature settings

At /admin/gitlab_duo/model_selection:

  • Assign a self-hosted model to General Chat (duo_chat feature) and Non-Agentic Code Review (review_merge_request).
  • Leave Agentic Chat (duo_agent_platform_agentic_chat) on the vendored default. That gap is exactly what stops :duo_chat from self-signing.
  1. Simulate the air-gap (no CustomersDot token)

gdk rails c

CloudConnector::ServiceAccessToken.delete_all CloudConnector::Tokens.cloud_connector_token.present? # => false

  1. Force the non-agentic mention path

gitlab:duo:setup enables all foundational flows, which routes mentions to the DAP handlers instead of the buggy chat path. On the test group/project, disable the code_review/v1 foundational flow (or duo_foundational_flows_enabled), then confirm:

project = Project.find_by_full_path('gitlab-duo/test')
user = User.find_by_username('root')

project.project_setting.update!(duo_foundational_flows_enabled: false)

Gitlab::Duo::CodeReview::ModeResolver.new(user: user, container: project).mode # => :classic
  1. Verify preconditions, then reproduce
Ai::FeatureSetting.feature_for_unit_primitive(:duo_classic_chat)&.self_hosted? # => true 
Ai::FeatureSetting.feature_for_unit_primitive(:duo_chat)&.self_hosted? # => nil/false ← bug trigger 
CloudConnector::Tokens.get(unit_primitive: :duo_chat, resource: :instance) # => nil
CloudConnector::Tokens.get(unit_primitive: :duo_classic_chat, resource: :instance) # => JWT
  1. Disable comment temprature

Feature.disable(:comment_temperature)

Tests

Without the code checked out

  1. On an MR add GitLabDuo as a reviewer. The review should complete without issues.
  2. tag GitLabDuo in a comment
    1. @GitLabDuo say 1
    2. Duo should reply with I'm sorry, I can't generate a response. Please try again.
    3. You should see an error similar to the below in gdk tail gitlab-ai-gateway Forbidden by auth provider
2026-09-14_16:40:24.76840 gitlab-ai-gateway       : {"url": "http://localhost:5052/v2/chat/agent", "path": "/v2/chat/agent", "status_code": 401, "method": "POST", "correlation_id": "01M2GCN1X3X7THGHBNYB9D3JM0", "http_version": "1.1", "client_ip": "127.0.0.1", "client_port": 34282, "duration_s": 0.0036475179986155126, "duration_request": 0.01215505599975586, "request_arrived_at": "2026-09-14T16:40:24.764628+00:00", "response_start_duration_s": 0.00359716999810189, "first_chunk_duration_s": 0.003627372996561462, "cpu_s": 0.0036468929999990962, "content_type": "application/json", "user_agent": "Ruby", "enabled-instance-verbose-ai-logs": false, "auth_duration_s": 0.0014917519984010141, "token_issuer": "", "auth_error_details": "Forbidden by auth provider", "http_exception_details": "Forbidden by auth provider", "gitlab_host_name": "gdk.test", "gitlab_version": "19.4.0", "gitlab_saas_duo_pro_namespace_ids": null, "gitlab_organization_id": "1", "is_gitlab_team_member": "false", "gitlab_language_server_version": null, "gitlab_feature_enablement_type": "duo_enterprise", "gitlab_feature_enabled_by_namespace_ids": "", "gitlab_global_user_id": "o88BIZtaiy6yM5lzp53iuic3QGZ0X+qp4fNmW5MXXJw=", "gitlab_root_namespace_id": "1000000", "gitlab_realm": "self-managed", "gitlab_instance_id": "c4353c96-092f-41dc-85c6-eb05436327a8", "logger": "api.access", "level": "info", "type": "mlops", "stage": "main", "timestamp": "2026-09-14T16:40:24.768334Z", "message": "127.0.0.1:34282 - \"POST /v2/chat/agent HTTP/1.1\" 401"}

With the code checkout and having ran gdk restart gitlab-ai-gateway

Duo should be able to respond to the direct message, or at least see an error from the model provider.

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.

Edited by Kaveh Nejad

Merge request reports

Loading
Loading