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
How to set up and validate locally
Environment set up
Prerequisites:
- AIGW set up locally
- gitlab runner
Set up steps
- 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.
- 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
- 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
- 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.
- Simulate the air-gap (no CustomersDot token)
gdk rails c
CloudConnector::ServiceAccessToken.delete_all CloudConnector::Tokens.cloud_connector_token.present? # => false
- 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- 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- Disable comment temprature
Feature.disable(:comment_temperature)
Tests
Without the code checked out
- On an MR add
GitLabDuoas a reviewer. The review should complete without issues. - tag
GitLabDuoin a comment@GitLabDuo say 1- Duo should reply with
I'm sorry, I can't generate a response. Please try again. - You should see an error similar to the below in
gdk tail gitlab-ai-gatewayForbidden 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.