PublicKeyCredential.getClientCapabilities() never settles on secure origins
Version info:
______ ,,
,.-"` | ,-` |
.^ || |
/ ,-*^| || |
; / | || ;-*```^*.
; ; | |;,-*` \
| | | ,-*` ,-"""\ \
| \ ,-"` ,-^`| \ |
\ `^^ ,-;| | ; |
*; ,-*` || | / ;;
`^^`` | || | ,^ /
| || `^^` ,^
| _,"| _,-"
-*` ****"""``
qutebrowser v3.7.0
Git commit:
Backend: QtWebEngine 6.10.2
based on Chromium 134.0.6998.208
with security patches up to 144.0.7559.96 (plus any distribution patches)
(source: api)
Qt: 6.10.2
CPython: 3.13.12
PyQt: 6.9.0
Qt wrapper info:
PyQt6: success
PyQt5: not imported
-> selected: PyQt6 (via autoselect)
colorama: 0.4.6
jinja2: 3.1.6
pygments: 2.19.2
yaml: 6.0.3
adblock: 0.6.0
objc: no
PyQt6.QtWebEngineCore: 6.10.0
PyQt6.sip: 6.11.1
pdf.js: 5.6.205 (/nix/store/ma9r0k6vyb1cw5bhln8y2qbj1hbfjm28-source/build/pdf.mjs)
sqlite: 3.51.2
QtNetwork SSL: OpenSSL 3.6.1 27 Jan 2026
Style: QFusionStyle
Qt Platform: wayland (/nix/store/zfjk8cllkx79ic8pnf1prwym5ik3fa1v-river-next-0.5.0-dev/bin/river -c /nix/store/3sma2x0xmw9kqr9mxhxf4a64nqs9a4l6-river-reka-init)
OpenGL: Intel, 4.6 (Compatibility Profile) Mesa 26.0.4
Platform: Linux-6.19.11-x86_64-with-glibc2.42, 64bit
Linux distribution: NixOS 26.05 (Yarara) (nixos)
Frozen: False
Imported from /nix/store/3d9gbgw3dn7sybx9vaz49cl5qp18vdp1-qutebrowser-3.7.0/lib/python3.13/site-packages/qutebrowser
Using Python from /nix/store/19lsw6qfzrsh7jbspwi0kibs7l860ywd-python3-3.13.12-env/bin/python3.13
Qt library executable path: /nix/store/9lf2mfhsk7s379mfsh8nqc4xabdg6dw6-qtbase-6.10.2/libexec, data path: /nix/store/9lf2mfhsk7s379mfsh8nqc4xabdg6dw6-qtbase-6.10.2Does the bug happen if you start with --temp-basedir?:
Yes.
I reproduced this with fresh temporary basedirs (--temp-basedir / -B $(mktemp -d)) and no existing qutebrowser profile state.
Description
PublicKeyCredential.getClientCapabilities() exists in qutebrowser/QtWebEngine, but on secure origins it never resolves or rejects for me.
That seems to break sites which await this promise before continuing their auth flow. The concrete user-facing symptom I ran into was the ChatGPT homepage login modal getting stuck after entering the email address: the modal submits, disables its buttons, and never redirects to the password step. A direct OpenAI auth URL works, so the breakage appears to be in the capability-detection step used by the homepage modal.
After tracing that site with CDP, I found the relevant code path is waiting on PublicKeyCredential.getClientCapabilities(). I then reduced it to a smaller qutebrowser repro on https://example.com/.
On a secure origin, this expression times out after 3 seconds instead of settling:
await Promise.race([
PublicKeyCredential.getClientCapabilities()
.then(v => ({ ok: true, v }))
.catch(e => ({ ok: false, err: String(e) })),
new Promise(r => setTimeout(() => r({ timeout: true }), 3000)),
])Actual result in qutebrowser for me:
{ timeout: true }Expected result:
- the promise should resolve with a capabilities object, or
- reject with an error,
- but it should not stay pending forever.
More evidence that this is the blocker: if I inject a document-start shim which replaces PublicKeyCredential.getClientCapabilities with an async function that immediately resolves to an all-false capabilities object, the exact same ChatGPT homepage login flow proceeds normally to the password page.
So this looks like a qutebrowser/QtWebEngine WebAuthn capability-detection hang rather than a site-specific login issue.
How to reproduce
Minimal API repro:
- Start qutebrowser with a fresh profile on any secure origin, for example:
qutebrowser --temp-basedir https://example.com/ - Open a JS console for that page (I used QtWebEngine remote debugging / CDP).
- Evaluate:
await Promise.race([ PublicKeyCredential.getClientCapabilities() .then(v => ({ ok: true, v })) .catch(e => ({ ok: false, err: String(e) })), new Promise(r => setTimeout(() => r({ timeout: true }), 3000)), ]) - Observe it timing out instead of settling.
Higher-level site symptom:
- Open
https://chatgpt.com/in a fresh qutebrowser temp-basedir. - Click the homepage
Log inbutton. - Enter an email address and submit.
- Observe that the modal stays on
chatgpt.com, disables its buttons, and never reaches the password page. - As a control, opening the direct auth URL (
https://auth.openai.com/log-in-or-create-accountorhttps://chatgpt.com/auth/login) does continue to the password page.
I searched qutebrowser issues for getClientCapabilities, PublicKeyCredential, conditionalGet, passkey, and related WebAuthn terms, but didn't find this exact hang already reported. This may ultimately belong in QtWebEngine, but I'm filing it here first since qutebrowser users hit the broken API directly.
Source: qutebrowser/qutebrowser