El secreto era correcto, el primer byte no: un BOM de UTF-8 tumbó el despliegue
Una CLI confirmó que el token se había subido a producción. El valor era el correcto. Todas las peticiones fallaban por un byte-order mark que PowerShell coló en la tubería.

El despliegue dijo que había ido bien y el token era el correcto, carácter a carácter. Y aun así cada petición a la API caía con el mismo mensaje: no se puede convertir el argumento a ByteString porque el carácter en el índice 7 tiene un valor de 65279. Ese número es U+FEFF, un byte-order mark. El índice 7 es justo el primer carácter después de Bearer . Algo había metido un BOM en el token entre el vault y el destino. El valor estaba bien; los bytes, no.
Lo peor es que no se podía comprobar a ojo. La variable estaba marcada como sensible, es decir, de solo escritura: el panel no la muestra, la CLI no la muestra y la API devuelve la fila con el valor omitido. Es una buena práctica de seguridad y a la vez un agujero de auditoría, y es la razón de que el diagnóstico costara una hora en lugar de un minuto. Había una credencial equivocada de una forma que nada podía enseñar, guardada en un sitio diseñado para que nadie, ni su dueño, pudiera leerla y contrastarla.
El fallo está en la tubería
El script leía el secreto del vault y lo pasaba por una tubería a la CLI de la plataforma. Ahí está el bug. En PowerShell 5.1, pasar una cadena por pipe a un comando nativo antepone U+FEFF, porque [Console]::OutputEncoding es UTF-8 con preámbulo de tres bytes en sesión no interactiva:
'SECRETVALUE' | node -e "process.stdin.on('data',d=>console.log(JSON.stringify(d.toString())))"
La salida empieza por U+FEFF antes de la S. Puedes comprobarlo tú mismo con [Console]::OutputEncoding.GetPreamble().Length, que devuelve 3. La CLI transmitió esos bytes fielmente y la plataforma los guardó con la misma fidelidad. Todo el camino aguas abajo era correcto respecto a un valor que ya venía mal.
Dos arreglos que no funcionan
El candidato obvio es forzar una codificación UTF-8 sin preámbulo en $OutputEncoding. No sirve: el BOM sigue ahí. El segundo candidato es hacerlo en [Console]::OutputEncoding con el mismo UTF8Encoding $false. Tampoco. Ambos son los arreglos que aparecen al buscar el problema, y se midieron antes de escribir código. Eso importó: el primer parche aplicado fue el de $OutputEncoding, con un comentario explicando con toda tranquilidad un arreglo que no hacía nada. Lo cazó una ejecución de control.
El que sí funciona
Hay que dejar de usar la tubería. Se escribe el valor en un fichero temporal con una codificación controlada, UTF8Encoding $false, y se redirige ese fichero al stdin del proceso con Start-Process -RedirectStandardInput. La verificación se hace a nivel de byte, que es la única que significa algo aquí: los cuatro primeros bytes del fichero son 83,69,67,82, o sea "SECR", sin preámbulo.
Dos detalles que aparecen por el camino. La CLI necesita --yes para sobrescribir una variable existente, porque si no sale con confirmation_required y no hay forma de responder al prompt: stdin está ocupado con el secreto. Y Start-Process no lanza directamente un shim de npm, así que toca pasar por %ComSpec%.
El bug de codificación es de un solo carácter y volverá a morder a otro. Lo que de verdad costó tiempo es otra cosa: un secreto de solo escritura no se puede auditar leyéndolo, y el almacenamiento sensible elimina la única comprobación que hace la mayoría. Si se usa, y se debería usar, la verificación tiene que ser de comportamiento: después de cada push, hacer una petición real con la credencial guardada y comprobar la respuesta. No vale un "Updated" de la CLI ni un updatedAt que se ha movido.

