Change:
The alert level of all TLS alerts is "fatal"; a TLS stack MUST NOT generate alerts at the "warning" level. <ins> QUIC permits the use of a generic code in place of a specific error code; see Section 11 of QUIC-TRANSPORT.  For TLS alerts, this includes replacing any alert with a generic alert, such as handshake_failure (0x128 in QUIC).  Endpoints MAY use a generic error code to avoid possibly exposing confidential information. </ins> 4.11. After QUIC moves to a new encryption level, packet protection keys

Discussion:
This was already permitted by the transport document, but it was not adequately explained. The example that tripped us up was ALPN, which has received a bit of attention here.
Sec 8.1. of the quic-tls draft states that, if an application protocol is not negotiated, endpoints MUST immediately terminate the connection with a noapplicationprotocol TLS alert (QUIC error code 0x178). For additional security, some TLS implementations choose to hide the exact reason for a handshake failure, and only issue a handshakefailure alert instead. The QUIC layer obtaining the "true alert" may be difficult or impossible. Maybe the requirement to send noapplication_protocol should be a SHOULD instead.
This is probably fine as the reason is observable. It is true that some TLS alerts are hidden, but that is generally because that leaks private information. But here the information is public, or at least shared by client and server. Is the concern that this is potentially sent in an Initial packet? Because that shouldn't be necessary, or even possible.
I'm not concerned with the security implications, or lack thereof, of sending noapplicationprotocol. My main concern is that we, and other implementations, will be non-compliant with the draft because we have no choice but to send a general handshakefailure alert in this case. Our TLS implementation doesn't allow us to choose which alerts are converted to handshakefailure before being reported to QUIC; it's all or nothing.
I'm not sure that I'm following this: you are sending a QUIC CONNECTIONCLOSE frame, not a TLS alert. Our stack makes the appropriate CONNECTIONCLOSE directly without talking to TLS.
I'm referring to the error code field in the QUIC CONNECTIONCLOSE frame. In the case of a crypto error, the QUIC error code for handshake failure is 0x128. The QUIC error code for no application protocol is 0x178. The draft mandates that we send 0x178. In our stack, TLS does the ALPN parsing and if there is a TLS alert, QUIC converts it to a CONNECTIONCLOSE with the reported error code; TLS alert 0x28 for handshakefailure, 0x78 for noapplicationprotocol.

Due to TLS converting all alerts during the handshake to handshakefailure, we end up sending a QUIC CONNECTION_CLOSE frame with 0x128, instead of 0x178.
I believe what you are asking is already catered for. The requirement for the failure case is MUST close, and the most appropriate error code is defined.
To follow up with a way to resolve this. I don't believe we need to change any normative language and so this is not a design issue. I'll leave it to editors' discretion on whether there is any editorial work required to address the original concern.
I'm still stuck on how the TLS code needs to be involved in this. Our code generates the alert, but does so .
NAME I think the problem is due to how certain implementations handle ALPN. In case of H2O, we handle ALPN in our onclienthello callback, that is invoked by picotls (URL). At this point, the call stack is H2O (application) -> quicly (QUIC stack) -> picotls (TLS stack) -> onclienthello (application callback). In this type of design, if the TLS stack is going to determine the error code (or change the error code returned from a callback), then it would be impossible (or difficult) for the QUIC stack to send the appropriate code.
I see. I hope that the application isn't the callback code, but that quicly is involved in this somehow; otherwise, you have just made H2O responsible for fulfilling QUIC requirements that you might assume quicly would be responsible for. In general, I would assume that you have access to QUIC transport state in your callback from the TLS stack. Let's say that you choke on the ALPN. You cause TLS to issue a generic alert (which is fine, as Lucas points out). At some later point that alert is going to hit QUIC code again so that it can be translated to a QUIC alert. If you can save the reason from the callback (presumably you need this capability to save transport parameters) then you can construct the right message. Of course, that's complicated. The generic alert is fine.
FWIW this particular error code is the one I've seen the most of during interop.

So I will make the observation that anyone building a stack that causes an ALPN failure to not be exposed, is going to cause a lot of problems for future QUIC as a generic transport layer.
NAME Yeah I think my preference goes to not having an abstraction layer in our QUIC protocol stack for hiding TLS handshake protocol API. I'd argue that the intent of using TLS in QUIC is to reuse TLS including its extensions when possible. Requiring (or recommending) a QUIC stack to provide abstraction for those extensions (that happen at the handshake protocol level) reduces that benefit. That said, I think we agree that in this particular case a generic alert is fine, and that what we need at most is an editorial improvement.
I agree that the normative requirements are fine. It would be good to expose the specific ALPN error, and we can make the necessary TLS change to detect this properly. However, "generic TLS alerts" are a user-configurable setting, and we're reluctant to just override customer preference just because it's in a CONNECTION_CLOSE instead of a TLS Alert. Moreover, when the TLS WG gets around to encrypting the ALPN, it is not clear that exposing this error is so benign anymore.

Moreover, when the TLS WG gets around to encrypting the ALPN, it is not clear that exposing this error is so benign anymore. Will ESNI/ECHO define a way to protect the confidentiality of such TLS alerts?
Not with the current designs, but we've been discussing encrypting more, in which case maybe?