Send originating namespace when categorizing Duo Chat questions

What does this MR do and why?

Duo Chat's categorize_question post-processing (the /v1/prompts/categorize_question AI Gateway call) sends a null root namespace. CategorizeQuestion did not override root_namespace, and the categorize sub-request is triggered with only the user as its resource (chat.rb passes user to ExecuteMethodService), discarding the namespace the parent chat already resolved.

So the x-gitlab-root-namespace-id header — the AI Gateway's ultimate_parent_namespace_id — comes through null, and duo_classic_chat categorization isn't attributed to the namespace the user was chatting in.

This is a data-attribution fix (billing/analytics), not a functional change — categorization still works.

Fix

The parent Chat completion already resolves the originating namespace (chat.rb: from the chat's resource, else the frontend-supplied root_namespace_id). This MR:

  1. Extracts that into a single memoized root_namespace method and forwards its id into the categorize service options.
  2. Overrides root_namespace in CategorizeQuestion to read the forwarded id.

Verification

Automated

  • chat_spec: asserts the categorize service options now include root_namespace_id.
  • categorize_question_spec: covers the completion's root_namespace (populated + nil branches).

Manual (local)

categorize_question is .com-only and disabled in development (response_post_processing returns early on Rails.env.development?), so exercising it locally needs three temporary changes:

  1. Simulate SaaS: GITLAB_SIMULATE_SAAS=1.
  2. Comment out return if Rails.env.development? in Chat#response_post_processing.
  3. Log the outgoing namespace headers in Gitlab::Llm::AiGateway::Client#perform_completion_request for categorize_question requests.

Then open classic Duo Chat (Agentic toggle off) from inside a project and ask a question. The categorize request now carries the project's root namespace instead of null:

url: /v2/prompts/categorize_question
x-gitlab-root-namespace-id: 24     # root group of the project the chat was opened in

The header-delivery path (Client → Gitlab::AiGateway.add_namespace_headers! → x-gitlab-root-namespace-id, read by the AI Gateway as ultimate_parent_namespace_id) is the same path verified end-to-end for the related session-title fix.

Limitation

For a genuinely context-free chat — no chat resource and no frontend root_namespace_id (e.g. a global page) — there is no originating namespace to send, so the value stays null by design.

Edited by Ankit Panchal

Merge request reports

Loading
Loading