Over in HttpStreamFactoryImpl, there's this logic to disable False Start for proxies and client certs:
https://cs.chromium.org/chromium/src/net/http/http_stream_factory_impl_job.cc?rcl=2ae7c2317f4f06daeb5201ac7f755db318d27528&l=1372
This is because we depend on proxy connection failures being mapped to ERR_PROXY_CONNECTION_FAILED, to hit this path when the proxy handshake fails.
https://cs.chromium.org/chromium/src/net/http/http_proxy_client_socket_wrapper.cc?rcl=2db34c22417d64205320d05815b99c58bef29873&l=533
https://cs.chromium.org/chromium/src/net/http/proxy_fallback.cc?rcl=d70bf37b08c43fa8307aae7fd90086bad16e5a53&l=11
https://cs.chromium.org/chromium/src/net/http/http_stream_factory_impl_job.cc?l=1390 (sets should_reconsider_proxy_)
https://cs.chromium.org/chromium/src/net/http/http_stream_factory_impl_job_controller.cc?l=1294
With False Start, the client certificate error will happen slightly later, in the first Read, disrupting this. Thus we disable False Start. (I think that code should be conditioned on is_proxy so we don't touch the origin's SSLConfig. Digging through get, it predates the two SSLConfigs getting split.)
However, TLS 1.3 always behaves like False Start in this regard, so I suspect we don't get this right. (Whoever grabs this bug should test this.)
In principle, it should be unambiguous where the error comes from because the server sends a 200 OK before the tunnel kicks in. I think we just need a bit of additional error mapping in HttpProxyClientSocketWrapper::Connect's state machine. (Note there are both HTTP/1.1 and HTTP/2 cases here. Or maybe we just post-process the result of the state machine, I dunno.)
At that point, I expect we can get rid of this False Start special-case.
Aside: If we ever need to distinguish a proxy-level error that happens after tunnel-establishment, that's still fine. We could do some error mapping in HttpProxyClientSocketWrapper::Read. But I don't think that's useful here.