Start Debugging

Solución: las variables de entorno de launchSettings.json se ignoran en un perfil con commandName: Executable

Si tu perfil Executable ejecuta dotnet run o dotnet watch, el comando interno aplica el perfil predeterminado y sobrescribe tus variables. Agrega --no-launch-profile y usa el SDK 10.0.200+.

Si tu perfil commandName: "Executable" lanza dotnet run o dotnet watch run, agrega --no-launch-profile (o --launch-profile <name>) a su commandLineArgs. De lo contrario, el comando interno elige el perfil predeterminado del proyecto, y las environmentVariables de ese perfil sobrescriben las que tu perfil Executable acaba de establecer. Si la CLI muestra “The launch profile type ‘Executable’ is not supported”, estás en un SDK anterior a 10.0.200. Actualiza, porque esos SDK omiten el perfil completo. Medí todo lo que sigue en macOS con los SDK 10.0.112, 10.0.302, 10.0.401 y 11.0.100-rc.1.

El error en contexto

Hay dos versiones de este problema, y cuál encuentras depende de tu SDK.

En un SDK 10.0.1xx (y en SDK anteriores, que solo entendían perfiles Project), dotnet run --launch-profile te lo dice directamente y después ejecuta el proyecto de todos modos:

Using launch settings from /src/app/Properties/launchSettings.json...
The launch profile "Exe" could not be applied.
The launch profile type 'Executable' is not supported.
MY_MODE=<null> DOTNET_ENVIRONMENT=<null> DOTNET_LAUNCH_PROFILE=<null> args=[] cwd=/src/app

Ese mensaje es fácil de pasar por alto porque la aplicación arranca. Solo que arranca sin ningún perfil: sin variables de entorno, sin commandLineArgs, ni siquiera DOTNET_LAUNCH_PROFILE.

En 10.0.200 y posteriores no hay advertencia. El perfil se ejecuta, pero la aplicación ve valores incorrectos. Este es el caso reportado en dotnet/sdk#56023: un perfil “Watch” establece ASPNETCORE_ENVIRONMENT=Development, y la aplicación sigue reportando Production. La única pista es que la línea “Using launch settings” se imprime dos veces:

Using launch settings from /src/app/Properties/launchSettings.json...
Using launch settings from /src/app/Properties/launchSettings.json...
MY_MODE=from-Default DOTNET_ENVIRONMENT=Production DOTNET_LAUNCH_PROFILE=Default args=[] cwd=/src/app

Por qué se pierden las variables

Estas son las causas, de la más común a la menos común:

  1. Un comando anidado del SDK vuelve a aplicar un perfil. dotnet run aplica correctamente un perfil Executable. Inicia executablePath con las environmentVariables del perfil ya establecidas. Pero cuando ese ejecutable es dotnet mismo (run, watch run), el proceso hijo es un dotnet run nuevo sin --launch-profile. Lee el mismo launchSettings.json, selecciona el primer perfil con un commandName compatible y establece las variables de ese perfil en el proceso de la aplicación. Las variables del perfil ganan sobre las heredadas (consulta SetEnvironmentVariables en RunCommand.cs), así que tus valores externos se reemplazan en silencio.
  2. El SDK es anterior a 10.0.200. La compatibilidad con Executable en dotnet run y dotnet watch llegó con dotnet/sdk#51727, fusionado en release/10.0.2xx el 12 de diciembre de 2025. Antes de eso, la CLI solo conocía commandName: "Project". Visual Studio siempre admitió perfiles Executable, por eso el mismo archivo “funciona en VS”.
  3. El IDE nunca lee perfiles Executable. La extensión de C# para VS Code documenta que “Only profiles with "commandName": "Project" are supported” en su configuración del depurador. Elegir un perfil Executable allí no hace nada con sus variables.

Cómo dotnet run elige un perfil y superpone variables

Conviene conocer el orden exacto que sigue la CLI, porque cada solución alternativa de abajo es solo una forma de controlar uno de estos pasos. En el SDK 10.0.200 y posteriores, dotnet run hace lo siguiente:

  1. Si pasas --no-launch-profile, no usa ningún perfil. Se detiene aquí.
  2. De lo contrario, busca Properties/launchSettings.json (My Project/launchSettings.json para VB, o <app>.run.json junto a una aplicación basada en archivo).
  3. Con --launch-profile <name> elige ese perfil. La búsqueda distingue mayúsculas y minúsculas primero, y luego recurre a una coincidencia que no las distingue. Sin la opción, elige el primer perfil cuyo commandName sea Project o Executable. Cualquier otro nombre de comando (IISExpress, Docker, DotNetCore) se omite.
  4. Construye el entorno del proceso hijo en tres capas. Primero, el entorno heredado del proceso dotnet. Luego DOTNET_LAUNCH_PROFILE, además de ASPNETCORE_URLS a partir de applicationUrl para los perfiles Project, y cada entrada de environmentVariables. Por último, cualquier -e KEY=VALUE de la línea de comandos. Las capas posteriores ganan.

El paso 4 es la razón por la que falla el caso anidado. El dotnet run externo coloca tus valores en la capa uno del dotnet run interno, y la capa dos del comando interno los reemplaza. Nada en el proceso interno sabe que fue iniciado desde un perfil de lanzamiento. Toda solución consiste en lograr que el paso 1 o el paso 3 del comando interno se comporte como necesitas.

Reproducción mínima

Una aplicación de consola que imprime lo que realmente recibió:

// .NET 10, C# 14 - Program.cs (ImplicitUsings enabled)
Console.WriteLine($"MY_MODE={Environment.GetEnvironmentVariable("MY_MODE") ?? "<null>"} " +
                  $"DOTNET_ENVIRONMENT={Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "<null>"} " +
                  $"DOTNET_LAUNCH_PROFILE={Environment.GetEnvironmentVariable("DOTNET_LAUNCH_PROFILE") ?? "<null>"} " +
                  $"args=[{string.Join(",", args)}] cwd={Environment.CurrentDirectory}");

Y un Properties/launchSettings.json con un perfil Project normal primero, y luego tres perfiles Executable:

{
  "profiles": {
    "Default": {
      "commandName": "Project",
      "environmentVariables": { "MY_MODE": "from-Default", "DOTNET_ENVIRONMENT": "Production" }
    },
    "Exe": {
      "commandName": "Executable",
      "executablePath": "dotnet",
      "commandLineArgs": "bin/Debug/net10.0/app.dll hello",
      "workingDirectory": "..",
      "environmentVariables": { "MY_MODE": "from-Exe", "DOTNET_ENVIRONMENT": "Development" }
    },
    "ExeDotnetRun": {
      "commandName": "Executable",
      "executablePath": "dotnet",
      "commandLineArgs": "run --no-build",
      "workingDirectory": "..",
      "environmentVariables": { "MY_MODE": "from-ExeDotnetRun", "DOTNET_ENVIRONMENT": "Development" }
    },
    "Watch": {
      "commandName": "Executable",
      "executablePath": "dotnet",
      "commandLineArgs": "watch run --non-interactive",
      "workingDirectory": "..",
      "environmentVariables": { "MY_MODE": "from-Watch", "DOTNET_ENVIRONMENT": "Development" }
    }
  }
}

Al ejecutar dotnet run --no-build --launch-profile <name> con cada SDK se imprimió esto:

Perfil10.0.11210.0.302 / 10.0.401 / 11.0.100-rc.1
Default (Project)from-Defaultfrom-Default
Exe (ejecuta app.dll)advertencia “not supported”, <null>from-Exe, Development, args [hello]
ExeDotnetRunadvertencia “not supported”, <null>from-Default, Production
Watchadvertencia “not supported”, <null>from-Default, Production (10.0.302)

La fila Exe muestra que la compatibilidad con perfiles Executable funciona en los SDK modernos. Las filas ExeDotnetRun y Watch muestran la sobrescritura: el comando interno reporta DOTNET_LAUNCH_PROFILE=Default, lo que significa que tomó por su cuenta el primer perfil.

La solución, paso a paso

  1. Revisa el SDK. Ejecuta dotnet --version en el directorio del proyecto, porque global.json puede fijar una banda anterior. Necesitas 10.0.200 o posterior para que dotnet run y dotnet watch respeten los perfiles Executable. En 10.0.1xx, mantén las variables en un perfil Project.

  2. Evita que el comando anidado elija un perfil. Si executablePath es dotnet y los argumentos comienzan con run o watch, agrega --no-launch-profile:

    // .NET SDK 10.0.200+ - Properties/launchSettings.json
    "Watch": {
      "commandName": "Executable",
      "executablePath": "dotnet",
      "commandLineArgs": "watch run --non-interactive --no-launch-profile",
      "workingDirectory": "..",
      "environmentVariables": { "MY_MODE": "from-Watch", "DOTNET_ENVIRONMENT": "Development" }
    }

    Con ese cambio, la aplicación imprimió MY_MODE=from-WatchNoProfile DOTNET_ENVIRONMENT=Development bajo dotnet watch en 10.0.302. DOTNET_LAUNCH_PROFILE sigue mostrando el nombre del perfil externo, porque el dotnet run externo lo estableció y nada lo sobrescribió.

  3. O apunta el comando anidado a un perfil específico. Si las variables ya viven en un perfil Project, haz referencia a él en lugar de duplicarlas:

    // .NET SDK 10.0.200+
    "ExeDotnetRunPinned": {
      "commandName": "Executable",
      "executablePath": "dotnet",
      "commandLineArgs": "run --no-build --launch-profile Dev",
      "workingDirectory": ".."
    }

    Esto imprimió MY_MODE=from-Dev DOTNET_ENVIRONMENT=Development DOTNET_LAUNCH_PROFILE=Dev. En esta configuración, el perfil interno es el dueño de las variables. Todo lo que pongas en las environmentVariables del perfil externo pierde cuando ambos perfiles establecen la misma clave.

  4. En VS Code, mueve las variables a launch.json. La extensión de C# solo lee perfiles Project y únicamente sus environmentVariables, applicationUrl y commandLineArgs. En su lugar, coloca un bloque env en una configuración de lanzamiento coreclr. De todos modos, los valores de launch.json tienen prioridad sobre launchSettings.json.

Detalles y casos similares

Un perfil Executable listado primero se convierte en el predeterminado y puede bifurcarse para siempre. En 10.0.200+, el perfil predeterminado es el primero cuyo commandName es Project o Executable (IsDefaultProfileType en LaunchSettings.cs, y la misma regla en dotnet watch). Si pones "commandLineArgs": "run --no-build" en el primer perfil y ejecutas un dotnet run simple, cada proceso hijo selecciona de nuevo el mismo perfil. En 10.0.302 conté 53 procesos dotnet run después de 12 segundos, antes de terminarlos. La solución con --no-launch-profile de arriba también rompe el ciclo. Mantener un perfil Project al inicio del archivo es un seguro barato.

dotnet run -e tampoco sobrevive al salto anidado. Revisé dotnet run -e KEY=VALUE en el SDK 10.0.112 y posteriores (consulta dotnet run -e). Sobrescribe el perfil para el proceso que inicia el dotnet run externo. Cuando ese proceso es otro dotnet run, el perfil predeterminado interno también lo sobrescribe: -lp ExeDotnetRun -e MY_MODE=from-cli siguió imprimiendo from-Default. Lo mismo aplica a un export simple del shell. MY_MODE=from-shell dotnet run -lp Default imprime from-Default, porque los valores del perfil de lanzamiento siempre le ganan a los heredados.

%VAR% se expande, $(Property) no (todavía). La CLI pasa cada valor por Environment.ExpandEnvironmentVariables, así que %HOME% funciona también en macOS y Linux. $(HOME) y ${HOME} pasan literalmente. Las propiedades de MSBuild como $(TargetPath) o $(ProjectDir) no se expanden en ningún SDK publicado que probé (10.0.302, 10.0.401, 11.0.100-rc.1). En lugar de una variable ignorada en silencio, obtienes An error occurred trying to start process '$(TargetPath)' ... No such file or directory. El ProjectLaunchTargetsProvider de Visual Studio sí las expande (según la documentación de perfiles de lanzamiento de project-system), por eso un perfil copiado de una configuración de VS falla en la CLI. dotnet/sdk#56074 agrega la expansión. Se fusionó en main el 4 de septiembre de 2026, pero a día de hoy no está en release/11.0.1xx-rc2 ni en ninguna banda de 10.0. Hasta que se publique, usa rutas relativas.

workingDirectory es relativo a la carpeta Properties, no al proyecto. La CLI lo resuelve con Path.Combine(Path.GetDirectoryName(launchSettingsPath), value), así que ".." significa el directorio del proyecto. Visual Studio y Rider lo resuelven de forma distinta, algo que dotnet/sdk#56129 está discutiendo actualmente. Para los perfiles Project, la CLI ignora por completo workingDirectory en los SDK actuales.

Hay una corrección para el caso anidado en revisión. dotnet/sdk#56087 hace que dotnet run establezca un marcador DOTNET_LAUNCH_PROFILE_APPLIED=1 en los procesos que inicia desde un perfil Executable. Entonces, un dotnet run anidado sin un perfil explícito omite el perfil predeterminado. Seguía abierta el 30 de septiembre de 2026. Incluso cuando se publique, solo cubre los perfiles iniciados a través de la CLI. El PR señala que los IDE que inician el perfil Executable directamente siguen necesitando --no-launch-profile.

hotReloadEnabled en un perfil Project no hace nada en dotnet run. El autor del reporte #56023 también lo notó. Hot reload viene de dotnet watch, no de una propiedad del perfil. Esa es exactamente la razón por la que la gente envuelve dotnet watch en un perfil Executable. Consulta en qué se diferencia dotnet watch de dotnet run para ver qué agrega el observador.

Relacionado

Fuentes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Volver