Artifact registry: Report a service outage in the page, not as a 503
What does this MR do and why?
When the Artifact Registry service does not answer, because it is down or because its domain stops resolving, the Artifact Registry UI served the instance-wide 503 page: the one titled "503: We're sorry, GitLab is currently unavailable". One dependency being unreachable read as the whole of GitLab being down. This was reported after a few minutes of the service not responding.
Both organization entry points resolve the registry on the server before they render, and both treated any failure to reach it as a 503:
ee/app/controllers/organizations/artifact_registry_controller.rb, the landing route/o/:org/-/artifact_registry/ee/app/controllers/organizations/artifact_registry_repositories_controller.rb, the app route/o/:org/-/artifact_registry/:slug/repositories
render_503 renders errors/service_unavailable in the errors layout, which carries no navigation, no organization context, and no mention of Artifact Registry.
The failure itself comes out of ArtifactRegistry::NamespaceMapping#registry, which reads the namespace through the client and rescues every ArtifactRegistry::Client::Error into a ResolutionFailure. All of these therefore became the full-page 503: a DNS failure, a refused connection, a timeout, a TLS failure, an AR 5xx or 429, an unreadable response body, an AR 401 or 403, and any other non-2xx. The failure is negative-cached per organization for a minute, so the 503 page also outlived the outage by up to that long.
The app already knows how to say this. repositories_list.vue renders "The Artifact Registry service is unavailable." whenever its query fails after mount, and the other views have the same state. The 503 was the only thing standing between a reader and it.
Changes
- The app route mounts instead of answering 503. It anchors the mount on the slug from the URL and lets the app report the outage where the list would be. No frontend code changed.
- Slug enumeration is still closed. The route normally requires the requested slug to equal the resolved one, so an unknown slug and one the viewer may not see are byte-identical 404s. Resolution cannot confirm the slug while it is failing, so only the syntax is checked, through
ArtifactRegistry::SlugValidator. Every well-formed slug gets the same response during an outage, and the GraphQL queries behind it then fail the same way too, so a mount the registry did not confirm says nothing about which slugs exist. Non-members never reach it: the gating concern answers 404 before anything is resolved. - The landing route renders an Artifact Registry page. It has no slug to anchor on, so it cannot mount the app. New view
ee/app/views/organizations/artifact_registry/unavailable.html.hamlputs a Pajamas empty state inside the organization layout, so the sidebar and breadcrumb survive. It usesillustrations/status/status-fail-md.svg, the illustration the container registry and Harbor connection-error states already use. - A slug-less registry on the landing route now answers 404. That case is an Artifact Registry 404, meaning a permanently absent namespace rather than an outage, and it is what the app route already answered it with. After this MR, Artifact Registry never calls
render_503. - Both responses are HTTP 200. The request succeeded and the outage is reported in the page. This follows the container registry, where
Registry::ConnectionErrorsHandlerre-renders:indexwith a flag, and the search error state. Artifact Registry client failures already reach error tracking throughArtifactRegistry::ErrorReporter, so observability does not rest on the status code. - Two new translatable strings, with
locale/gitlab.potregenerated.
Test coverage:
ee/spec/requests/organizations/artifact_registry_controller_spec.rb: the two contexts that expected a 503, an authorization failure and an unreachable service, now expect the unavailable page. A new context covers the Artifact Registry 404 answering not-found.ee/spec/requests/organizations/artifact_registry_repositories_controller_spec.rb: the context that expected a 503 now expects the mount. New contexts cover the mount anchoring on the requested slug when it is not the one that would resolve, and a 404 for a slug that is not well formed.- 56 examples across those two files pass locally. RuboCop and haml-lint are clean. No Jest changes: the in-app alert is already covered in
repositories_list_spec.js.
Screenshots or screen recordings
How to set up and validate locally
-
Enable the
artifact_registry_uiandorg_stage_experimentalflags, and have an organization with an Artifact Registry namespace mapping row. -
Simulate the outage by writing a failure into that mapping's registry cache entry, which is the state a real outage leaves behind. In
rails console, withidbeing the mapping's id:Rails.cache.write( ['artifact_registry', 'namespace_mapping', 'registry', id], { error: { class: 'ArtifactRegistry::Client::UnavailableError', status: nil, request_id: nil, code: nil, message: 'Artifact Registry request failed' } }, expires_in: 30.minutes ) -
Load
/o/<org>/-/artifact_registry/. Expect the unavailable page inside the organization, not the instance-wide 503 page. -
Load
/o/<org>/-/artifact_registry/<slug>/repositories. Expect the app to mount and show the "The Artifact Registry service is unavailable." alert under the page heading. -
Rails.cache.deletethe same key to end the simulated outage.
Both were verified this way on the GDK; the screenshots above are from that run.
References
Reported directly rather than through an issue, so there is no tracking issue to close.
Agent Notes
Three things I left alone deliberately, each worth a second opinion:
- The sidebar item disappears during an outage.
Sidebars::Organizations::Menus::ArtifactRegistryMenu#render?needs either a resolved slug or an organization that has not been set up, and an outage gives neither, so the Artifact Registry entry vanishes from the organization sidebar even while the reader is looking at an Artifact Registry page. This reproduces locally and is visible in the screenshots above. It is the same root treatment, a failed resolution read as "no registry", but fixing it is a separate behavior and copy decision, so it is not in this MR. - The app route still offers "New repository" next to the alert. Creating one during an outage would fail. That is pre-existing behavior of the list component.
- The unavailable page has no retry button. The description tells the reader to refresh. Happy to add one if design wants it.
The judgment call I would most like reviewed is serving 200 rather than 503. It keeps the two Artifact Registry routes consistent and keeps the page out of reach of edge-level error handling, and error tracking already sees the failure, but it does mean the outage no longer shows up as a 5xx in request metrics.
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.
This MR was authored with the assistance of an AI coding agent and reviewed by @zcuddy before submission.


