rfc6749 says, "Before initiating the protocol, the client registers with the authorization server". It describes the client_id as REQUIRED. It gives some hints towards why this is the case, but leaves open questions (to me). It is also unclear to what extent they apply to PKCE because rfc7636 only mentions that the client_id should be considered public.
With the client_id being public, an attacking application that tried to gain authorization can easily send the client_id of the real application. That means, the authorization server cannot use the client_id alone to trust the client, nor can it use the client_id to show the user which client tries to gain authorization.
rfc6749 mentions that the client_id is used to select the redirection endpoint. On the other hand, the malicious application can override that endpoint during the authorization request -- so the authorization server cannot rely on that endpoint being correct and must ask the user if it is correct -- and also it seems redundant to select an endpoint using client_id if it can be specified in the authorization request. Anyway, selecting an endpoint using client_id is only possible if that endpoint depends only on the client and not on the user, but I'm interested in the case that each user runs a local-network installation of the client on a user-chosen address. Unless I force each user to register a separate client (which does not seem to be the intention of registration and client_id), this means that each user has a different redirection endpoint, so it has to be specified during the authorization request and cannot be selected by client_id alone.
rfc6749 also mentions the use of client_id during the token request: "In the "authorization_code" "grant_type" request to the token endpoint, an unauthenticated client MUST send its "client_id" to prevent itself from inadvertently accepting a code intended for a client with a different "client_id"." I honestly don't understand what this means. Assuming that the attacking client uses a different client ID to gain authorization (which in itself would be a serious problem, but let's ignore that for a moment) and then convinces the genuine application to use that code to get a token. Sending client_id would prevent that, but let's see what happens if we skip that check. Now the genuine application has a token which it did not ask for, giving it different permissions than it asked for. How is that a problem? The genuine application either has all the permissions it needed (plus some), working as intended, or it is lacking some permissions and will at some point fail with an error. To me this does not seem to do any more harm than to annoy the user.
Given all that, I do not understand why client registration and using a client_id is required. Suppose the client and the authorization server just skipped that and implemented the whole process without registration and client_id, what kind of attack would become possible?