Think I found the root cause of the bug. Some notes:
- The normal layers are computed correctly when GEO-bird_body is evaluated.
- Later in the BKE_object_get_evaluated_mesh function that is called by the transfer modifier, the mesh was copied. The copy created by mesh_copy_data does not include the normal layers because CD_MASK_NORMAL is not in CD_MASK_MESH or CD_MASK_DERIVEDMESH.
- The copy happens because in another thread geometry nodes is using the evaluated mesh of GEO-bird_body while evaluating another object. Since now the mesh is shared (or rather the MeshComponent actually), the mesh has to be copied when geometry_set_eval->get_mesh_for_write is called.
- Mid term, BKE_object_get_evaluated_mesh should probably return a const mesh. Theoretically, it should only be changed when updating caches (like the batch cache) which are safe to update, because they are derived data and do not actually change the mesh. Adding derived data to a mesh should generally be protected by a mutex. For batch cache that might not be necessary when we know that only the main thread adds the batch cache. There seems to be some other code that modifies the mesh returned by BKE_object_get_evaluated_mesh, that needs to be investigated further. It's also a bit weird that BKE_object_get_evaluated_mesh takes a const Object * but returns a non-const Mesh *.
- For now just use get_mesh_for_read with a const_cast instead of using get_mesh_for_write. That should match the old behavior without GeometrySet.