Fix Code Suggestions direct access on cloud-connected instances
What does this MR do and why?
On GitLab.com, POST /api/v4/code_suggestions/direct_access always returns a 400 error ("No Code Completion model provided"), even when the customer assigned a model through namespace-level Model Selection. Because of this, the language server cannot use the direct connection to the AI Gateway and falls back to the slower, indirect path through the monolith.
A first fix (!248253 (merged)) was reverted (!248825 (merged)) because it caused an incident (INC-12778): the token requests did not carry a root namespace, so CustomersDot rejected the usage billing checks with HTTP 422 "Root namespace can't be blank".
This MR fixes both problems together, in three commits:
- Send namespace context on the token request. The token request to the AI Gateway now includes the
x-gitlab-root-namespace-id,x-gitlab-namespace-id, andx-gitlab-project-idheaders. The client resolves them from the project and the user's governing namespace — the same way the completions endpoint andai_gateway_public_headersalready do. The AI Gateway trusts these headers only on this server-to-server call (SaaS realm) and embeds the root namespace as a claim in the JWT it issues. This gives CustomersDot a verified value for the usage billing check. - Run the endpoint checks before the token request. The feature, paid-namespace, and Duo-context checks now run first. The endpoint never requests a token for a user without a governing namespace — it returns 422 (
missing_default_duo_group) instead. - Allow direct access tokens on cloud-connected instances, behind a feature flag. The 400 guard from !237255 (merged) checks the
Ai::FeatureSettingrecord, which exists only on instances that use Duo Self-Hosted. The guard now applies only when a self-hosted AI Gateway is configured. Without one, the token request falls back to the cloud connector, as it did before 19.1. The new behavior is gated by thecode_suggestions_direct_access_cloud_connectedfeature flag (default off, actor: user). With the flag off, GitLab.com keeps the current production behavior, so rollback is instant.
The error message for the real Duo Self-Hosted case ("unpinned model") is kept.
Rollout and rollback
- The feature flag rolls out gradually (team members → percentage of users). See the rollout issue.
- Rollback = disable the feature flag. No revert is needed.
- During rollout, watch the CustomersDot quota-denials dashboard and the AI Gateway
USAGE_QUOTA_CHECK_TOTAL{result="deny", realm="saas"}metric.
Out of scope
Hardening on the AI Gateway side (prefer the JWT claim over the client-editable header on direct calls, fail safely when the namespace is absent) is tracked separately.
References
- Resolves #605887 (closed) (customer bug — root cause analysis in this comment)
- Resolves #608436 (closed) (long-term fix for the incident)
- Corrective action: gitlab-com/gl-infra/production-engineering#29494 (closed) (INC-12778)
- History: guard introduced in !237255 (merged) → first fix !248253 (merged) → revert !248825 (merged)
- Namespace headers pattern for the completions path: !231121 (merged)
Screenshots or screen recordings
No UI changes.
How to set up and validate locally
Cloud-connected path (the fix)
-
Make sure your GDK simulates SaaS (
export GITLAB_SIMULATE_SAAS=1) and has no self-hosted AI Gateway URL:# rails console Gitlab::CurrentSettings.update!(ai_gateway_url: nil) ::Ai::FeatureSetting.find_by(feature: :code_completions)&.destroy -
Enable the feature flag for your user:
Feature.enable(:code_suggestions_direct_access_cloud_connected, User.find_by(username: 'root')) -
Call the endpoint with a user who has a Duo seat and a default Duo namespace:
curl -X POST -H "Authorization: Bearer $TOKEN" \ "http://gdk.test:3000/api/v4/code_suggestions/direct_access" \ -H "Content-Type: application/json" \ -d '{"project_path": "group/project"}' -
Expected: 201 with a token. The outgoing token request to the AI Gateway carries
x-gitlab-root-namespace-id. With a local AI Gateway and its CustomersDot mock server (lib/usage_quota/mock_server.pyinai-assist), the usage quota check passes. -
Disable the feature flag and repeat: the endpoint returns 400 again (current production behavior).
Self-hosted path (behavior is kept)
-
Configure a self-hosted AI Gateway URL:
Gitlab::CurrentSettings.update!(ai_gateway_url: 'http://localhost:5052') -
Keep the code completions feature setting unpinned (no record).
-
Call the same endpoint — it still returns the 400 error with the clear message, same as before this MR.
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.