Publico esto porque me costó un par de horas y falla silenciosamente, que es la peor combinación. Si enrutas algo importante a través de un nodo IF o Switch en un booleano, esto vale diez minutos de tu tiempo.
Hay algunos problemas abiertos y temas del foro que describen este comportamiento sin una causa raíz adjunta. Esto es lo que encontré.
El síntoma
Tenía un nodo Switch decidiendo si enviar una alerta. La salida del nodo anterior se veía así:
{ “shouldEscalate”: true }
Condición del Switch: value:
{{ $(‘Extract Response’).first().json.shouldEscalate }}
operator: Boolean → is true
Cada ejecución se enrutó como false. Sin error. La ejecución se reportó como exitosa. La alerta simplemente nunca se disparó.
Qué no era
Perdí tiempo en hipótesis incorrectas primero, así que aquí están para ahorrarte el viaje:
• No era .first() vs .item — lo verifiqué
• No era un problema de multi-run/item-lineage
• No era el nodo anterior produciendo el valor incorrecto
Probé lo último soltando un nodo Set directamente antes del Switch que emitiera el valor sin procesar más su tipo: { “val”: true, “type”: “boolean”, “runs”: 1 }
Booleano genuino. Una sola ejecución. Valor correcto llegando al nodo. Y el Switch aún lo envió como false.
La causa real
n8n renderiza la salida de la expresión {{ }} como texto. Así que un booleano verdadero real llega al operador como la cadena “true” — y si hay algún espacio en blanco final o una nueva línea en el campo de parámetro después de los }} de cierre (muy fácil de introducir al pegar), obtienes "true ".
El operador Boolean estricto obtiene "true ", que no es un booleano, y la comparación falla. Silenciosamente, en la dirección false.
Eventualmente el nodo lo mostró literalmente: Wrong type: 'true ’ is a string but was expecting a boolean
Por qué los arreglos obvios no funcionaron
Intenté convertir dentro de la expresión:
{{ String($(‘Extract Response’).first().json.shouldEscalate).trim().toBoolean() }}
Aún falló. El espacio en blanco no está dentro de la expresión — es introducido después de }} durante el propio renderizado de n8n. Nada de lo que hagas dentro de las llaves puede alcanzarlo.
Alternar “Convert types where required” cambia el modo de fallo en lugar de arreglarlo. En mi caso, una configuración enrutaba todo como true y la otra enrutaba todo como false. Ambas son incorrectas; una es simplemente más ruidosa.
El arreglo que funciona
Deja de comparar el booleano. Emite una palabra clave explícita y coincide con texto:
value: {{ $(‘Extract Response’).first().json.shouldEscalate ? “ESCALATE” : “NORMAL” }}
operator: String → contains
match: ESCALATE
convert types: off
"ESCALATE " aún contiene “ESCALATE”. "NORMAL " no lo contiene de ninguna manera. El espacio en blanco final se vuelve inofensivo en lugar de fatal.
Verificado en ambas direcciones en la misma compilación — condición true enruta true, condición false enruta false. Vale la pena hacerlo en ambas direcciones; probar solo la dirección que esperas es medio test, y este bug es específicamente capaz de pasar una dirección mientras falla en la otra.
La versión general
Si un nodo Switch o IF está tomando una decisión que realmente importa — alerta, escalación, puerta de cumplimiento, cualquier cosa donde silenciosamente tomar la rama incorrecta es costoso — no la enrutes en un booleano sin procesar. Renderiza una cadena centinela y coincide con ella.
“El modo de fallo aquí no es un error. Es una ejecución verde que hizo lo incorrecto.”