The String is gone before you use it. (String("barn-") + WiFi.macAddress()) makes a temporary that’s destroyed at the end of that line, so clientId points into freed memory. Whatever the heap does next ends up in your client ID. Bit me on a sensor board years ago and I wasted a day too.
Same trap with setServer(host.c_str(), ...): PubSubClient keeps the pointer, not a copy, so if host is a local String in setup() you’ll get odd reconnect failures later.
Use buffers that live as long as the client:
static char clientId[32];
static char brokerHost[64];
void setupMqtt() {
uint8_t mac[6];
WiFi.macAddress(mac);
snprintf(clientId, sizeof(clientId), "barn-%02x%02x%02x", mac[3], mac[4], mac[5]);
strlcpy(brokerHost, settings.host.c_str(), sizeof(brokerHost));
mqtt.setServer(brokerHost, 8883);
}
// later
mqtt.connect(clientId, settings.token.c_str(), "");
settings.token.c_str() is fine inside the call as long as settings lives globally.