How is SegWit a soft fork? Reading this section of BIP144, I noticed the followng statement:


  Parsers supporting this BIP will be able to distinguish between the old serialization format (without the witness) and this one. The marker byte is set to zero so that this structure will never parse as a valid transaction in a parser that does not support this BIP. If parsing were to succeed, such a transaction would contain no inputs and a single output.


And this is in fact confirmed by this line in Bitcoin Core which is executed for every transaction found in a block during the CheckBlock call.

As far as I understand, this should mean that old clients that see a block containing a SegWit transaction would consider that transaction invalid and hence discard the whole block. Is that correct? What am I missing?
You're confusing transactions (the abstract object) and their serialization (the bytes on the wire in the P2P protocol or on disk).

Sure, SegWit introduces an extension to the P2P protocol (BIP144), which relays witnesses along with transactions, and old clients wouldn't understand such messages.

But old clients don't see them. Witnesses are only included when the requester asks for them, which old clients don't do. For them, new clients remove the witnesses before relay. Exactly because the witness of a transaction does not contribute to its txid, it is in fact possible to remove them before relay, without invalidating them. That is what makes it a soft fork.