Refuse Code Suggestions and Amazon Q requests on an offline license
What does this MR do and why?
This is the third of three merge requests, split from one change to limit blast radius. It moves Code Suggestions and Amazon Q onto the resolver.
Depends on !260773 (merged), the resolver MR, which adds Gitlab::AiGateway.request_url!, access_token_url! and NoCloudConnectorTokenError.
Depends on !265183, the chat and completions callers MR, which makes the resolver follow the token decision.
The merge order is !260773 (merged), then !265183, then this MR, which closes the work item.
On an offline cloud license, a GitLab Duo request can reach cloud.gitlab.com when Local AI Gateway URL is nil or a feature uses a GitLab-managed model. It carries no credential that host issued.
On an offline cloud license, the resolver now refuses a request, naming the feature setting, unless CloudConnector::Tokens#use_self_signed_token? would self-sign the token.
Code Suggestions and Amazon Q now resolve their URL through the resolver. Each of the three callers, the Code Suggestions base URL, the direct access token and the Amazon Q client, passes the unit primitive its token uses, so the resolver and the token decide on the same feature setting. When the resolver refuses, the caller stops before the request, in the failure shape its own callers already handle. The message names the setting to change.
- Code Suggestions completions and the token request return a 400.
- Amazon Q reports it where each caller reports a failure: the admin settings form, the health check, the
/qfailure note, or a log entry.
What changes for users:
- A refused Code Suggestions completion names its setting, Code Generation or Code Completion.
- With
cache_code_suggestions_direct_access_tokenon, a cached direct access token is now refused on an offline cloud license the same way as a fresh one, with the token step's 400 instead of a 500. - Otherwise, direct access and Amazon Q behave as before. Their specs now pin the unit primitive the resolver asks about.
Where each request resolves its URL, and how it reports the refusal
| Request | Resolved at | Unit primitive passed | Result on an offline cloud license |
|---|---|---|---|
Code Suggestions completions, POST /api/v4/code_suggestions/completions |
ee/lib/code_suggestions/model_details/base.rb, rescued in ee/lib/api/code_suggestions.rb |
:complete_code or :generate_code, the task's, or :amazon_q_integration when Amazon Q is connected |
a 400 in the shape of the existing missing-feature-setting error |
Code Suggestions token, POST /api/v4/code_suggestions/direct_access |
ee/lib/gitlab/llm/ai_gateway/code_suggestions_client.rb |
:complete_code |
the same 400, also when its raw Code Completion row is GitLab-managed |
| Amazon Q | ee/lib/gitlab/llm/q_ai/client.rb |
:amazon_q_integration |
NoCloudConnectorTokenError, which each caller reports as its own failure: an admin settings error on :base (create_service.rb, destroy_service.rb under ee/app/services/ai/amazon_q/), a failed health check result (ee/app/services/cloud_connector/status_checks/probes/amazon_q/end_to_end_probe.rb), the /q failure note (amazon_q_trigger_service.rb), or a log entry for a webhook event (ee/app/models/concerns/integrations/base/amazon_q.rb) |
Refusal path
flowchart LR
R["a Duo request<br/>builds its URL"] --> RU["Gitlab::AiGateway.request_url!<br/>or access_token_url!<br/>with the caller's unit primitive"]
RU --> L{"offline<br/>cloud license?"}
L -->|no| S["URL returned<br/>request sent"]
L -->|yes| U{"Local AI Gateway<br/>URL set?"}
U -->|no| E["raise NoCloudConnectorTokenError<br/>naming the setting<br/>no request sent"]
U -->|yes| T{"would the token<br/>be self-signed?"}
T -->|yes| S
T -->|no| E
E --> X["each caller handles it<br/>in its own failure shape"]
X --> CS["Code Suggestions API and CodeSuggestionsClient<br/>400"]
X --> Q["QAi::Client callers<br/>settings error, probe result, /q failure note, log"]An AI coding assistant wrote this change and ran the checks below in a simulated environment: a GitLab source checkout and a 19.4.1 test instance.
References
- Closes #629912.
- Split from !260773 (merged) at the request of its reviewer, @wanpol.
- Depends on !260773 (merged), the resolver MR.
- Depends on !265183, the chat and completions callers MR.
- Merge order: !260773 (merged), then !265183, then this MR.
Screenshots or screen recordings
No UI change. The health check screenshots are in the resolver MR.
How to set up and validate locally
Steps, and what they showed on 19.4.1
- With an offline cloud license, assign a self-hosted model to Code Completion and clear Local AI Gateway URL.
POST /api/v4/code_suggestions/completionswith acurrent_file, andPOST /api/v4/code_suggestions/direct_access. Expected: the requests return a 400 naming Local AI Gateway URL only, and nothing connects tocloud.gitlab.com.- Set the URL and repeat. Expected: the local AI gateway serves the completion.
- Set Code Completion to
vendoredwithAi::FeatureSettings::UpdateServicein a Rails console and repeat the token request. Expected: a 400 naming the model assignment only. bundle exec rspecon the changed spec files.
| On 19.4.1 with an offline cloud license | Before | With this change |
|---|---|---|
| Token request, URL set, Code Completion GitLab-managed | reached cloud.gitlab.com, 401 |
the 400 naming Code completion |
| Completions request, URL cleared, Code Completion self-hosted | reached cloud.gitlab.com, 401 |
the 400 naming the URL, as did the token request |
| Completions, URL set, self-hosted model | the local AI gateway served completions | the same |
These instance results come from the combined change in the original merge request, before it was split. Amazon Q rests on its specs alone, since it needs an Amazon Q connection to AWS.
- On a test VM with this MR applied on top of the chat and completions callers MR, the 8 changed spec files and
amazon_q_specpass with 406 examples, no failures and 12 pending. No changed line lacks coverage. - The 14 spec files that MR changes pass with 569 examples and no failures with this MR on top.
- With the unit primitive reverted in the three callers, the 4 specs that pin it fail on 8 of 295 examples.
- With the offline check run after the token cache read, as before this change, the two cached-token examples fail: the client returns the cached token, and the endpoint answers 500.
- RuboCop is clean on the 18 Ruby files changed against the callers 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.