Websocket requests (impacting Classic Duo Chat) can hang when using Geo URL
Summary
When using Classic Duo Chat with Geo, and unified URL is configured, Duo Chat doesn't work and just hangs.
This is because the Geo secondary proxy returns 404 errors for ActionCable websocket connections /-/cable when proxying connections for Classic Duo Chat. As a result, the websocket connection breaks.
Steps to reproduce
- Use GET to setup a Geo environment - 1 node in each site is sufficient, and ensure unified URL is configured
- Enable Classic Duo Chat (you can link to the staging AIGW)
- On your localhost, update
/etc/hostsand point your unified URL directly to the primary site - Try using Classic Duo Chat - it works fine
- On your localhost, update
/etc/hostsand point your unified URL directly to the secondary site - Try using Classic Duo Chat - it breaks
In a real environment, I suspect what happens is that the connection flip flops between Geo sites. So classic Duo Chat may work initially and then appear to hang, and refreshing the page will then fix it temporarily.
Edit:
To replicate: On the secondary site, open an existing issue in a tab. In another tab, add some labels to the same issue. See the cable request failing in the first tab.
Example Project
N/A
What is the current bug behavior?
When using Classic Duo Chat with Geo, and unified URL is configured, Duo Chat doesn't work.
Edit: Websocket request fail on Geo secondaries
What is the expected correct behavior?
When using Classic Duo Chat with Geo, and unified URL is configured, Duo Chat should work.
Edit: Websocket requests work on Geo secondaries
Relevant logs and/or screenshots
# Secondary site
{"backend_id":"rails","content_type":"text/html; charset=utf-8","correlation_id":"01K8W8ZZHXSHD7GW32EQ9KJ6FJ","duration_ms":44,"host":"primary.example.com","level":"info","method":"GET","msg":"access","proto":"HTTP/1.1","read_bytes":1224,"referrer":"","remote_addr":"127.0.0.1:0","remote_ip":"127.0.0.1","route":"^/-/cable\\z","route_id":"geo_action_cable","status":404,"system":"http","time":"2025-10-31T04:39:12Z","ttfb_ms":44,"uri":"/-/cable","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36","written_bytes":838}
{"backend_id":"rails","content_type":"text/html; charset=utf-8","correlation_id":"01K8W90A9KGPN57J5AJTNZEHGH","duration_ms":69,"host":"primary.example.com","level":"info","method":"GET","msg":"access","proto":"HTTP/1.1","read_bytes":1224,"referrer":"","remote_addr":"127.0.0.1:0","remote_ip":"127.0.0.1","route":"^/-/cable\\z","route_id":"geo_action_cable","status":404,"system":"http","time":"2025-10-31T04:39:23Z","ttfb_ms":69,"uri":"/-/cable","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36","written_bytes":838}
# Primary site
{"backend_id":"rails","body_limit":104857600,"content_type":"text/html; charset=utf-8","correlation_id":"01K8W90A9WCGN94YY9GNP493JW","duration_ms":52,"host":"primary.example.com","level":"info","method":"GET","msg":"access","proto":"HTTP/1.1","read_bytes":1881,"referrer":"","remote_addr":"34.129.180.129:0","remote_ip":"34.129.180.129","route":"^/-/","route_id":"dash","status":404,"system":"http","time":"2025-10-31T04:39:23Z","ttfb_ms":51,"uri":"/-/cable","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36","written_bytes":1591}
{"backend_id":"rails","body_limit":104857600,"content_type":"text/html; charset=utf-8","correlation_id":"01K8W90QZT76CT0NTXX3SVN8H3","duration_ms":27,"host":"primary.example.com","level":"info","method":"GET","msg":"access","proto":"HTTP/1.1","read_bytes":1881,"referrer":"","remote_addr":"34.129.180.129:0","remote_ip":"34.129.180.129","route":"^/-/","route_id":"dash","status":404,"system":"http","time":"2025-10-31T04:39:37Z","ttfb_ms":26,"uri":"/-/cable","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36","written_bytes":1591}
Output of checks
Results of GitLab environment info
Expand for output related to GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of: `sudo gitlab-rake gitlab:env:info`) (For installations from source run and paste the output of: `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
Results of GitLab application Check
Expand for output related to the GitLab application check
(For installations with omnibus-gitlab package run and paste the output of:
sudo gitlab-rake gitlab:check SANITIZE=true)(For installations from source run and paste the output of:
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)(we will only investigate if the tests are passing)
Possible fixes
I suspect Workhorse is not adding the correct headers for sockets back to the primary site:
- https://gitlab.com/gitlab-org/gitlab/-/blob/d9d81ce559a8989d3f591ab3289f317e2045fec2/workhorse/internal/upstream/upstream.go#L284-285
- https://gitlab.com/gitlab-org/gitlab/-/blob/2302ad09ffadcdb6e0451c29d25c35b3af56759b/workhorse/internal/proxy/proxy.go#L77-91
Workaround
- On your localhost, update
/etc/hostsand point your unified URL directly to the primary site
Patch release information for backports
If the bug fix needs to be backported in a patch release to a version under the maintenance policy, please follow the steps on the patch release runbook for GitLab engineers.
Refer to the internal "Release Information" dashboard for information about the next patch release, including the targeted versions, expected release date, and current status.
High-severity bug remediation
To remediate high-severity issues requiring an internal release for single-tenant SaaS instances, refer to the internal release process for engineers.

