The best way to look at it is that yes, you’ll need to encode & to %26 to include it within the status body of a message. In an OAuth signature base string, then already encoded “%26” would need to be “%25%26.”
The library or environment you are using might have some features to it that is trying to “do the right thing” for you – but actually isn’t. Perhaps when you use a raw “&” it converts it %26 for the POST body but fails to convert it to %2526 for the signature basestring. Perhaps when you send a %26 instead, it converts it to %2526 for your POST body and then %252526 for your signature base string, which would explain the encoding issue in the resultant tweet.
Identifying when there’s magic behavior going on should help you solve the problem.