Declarative Pipeline environment variable precedence undocumented between top‑level and stage‑level scopes
0 reputation · 26 May 2024, 05:41 UTC
0 reputation · 26 May 2024, 05:41 UTC
Determine the authoritative precedence rule when a Declarative Pipeline defines the same environment variable at both the top‑level environment directive and a stage‑level environment block.
The Jenkins documentation describes inheritance but does not explicitly state which scope wins when names collide. Empirical testing across Jenkins 2.361, 2.372, and 2.385 shows stage‑level values take effect inside that stage, yet the absence of a documented guarantee leaves the behavior vulnerable to silent changes. Additionally, variable resolution can differ between shell steps (sh), Groovy steps, and the withEnv step can temporarily mask the underlying precedence.
sh, bat, and Groovy env.VAR accesses within the same stage?when conditions, options, or agent directives) that evaluate the variable before the stage‑level override takes effect?When a Declarative Pipeline declares the same variable name in a top‑level environment block and again inside a stage‑level environment block, the value defined at the stage level takes precedence for every step that runs inside that stage. This includes shell steps (sh / bat), Groovy env.VAR accesses, and withEnv wrappers.
The Declarative Pipeline specification merges the environment maps in a hierarchical order: the top‑level map is inherited by all stages, and each stage’s map is applied on top of the inherited one. Keys that appear in the stage map override those from the parent. The merge happens before the stage body begins execution, so all steps inside see the overridden value.
sh / bat – the shell receives the overridden variable.env.VAR – the value is the stage override.withEnv – the temporary environment inherits the stage override first.Constructs that are evaluated before the stage body executes (e.g., when conditions, options, and agent directives) see only the top‑level environment. The stage‑level override is not yet in effect at that point, so any variable reference in those constructs will use the top‑level value.
pipeline {
agent any
environment {
MY_VAR = 'top'
}
stages {
stage('Test') {
environment {
MY_VAR = 'stage'
}
steps {
script {
echo "Inside stage: ${env.MY_VAR}"
}
sh 'echo Inside shell: $MY_VAR'
}
}
}
}
stage for both the Groovy and shell outputs.when block that references env.MY_VAR and confirm it sees top.
sh, bat, Groovy env.VAR, and withEnv inside the stage.when, options, agent) will only see the top‑level value because the stage override is not yet applied.Does your pipeline use any when conditions that reference the variable in question? Knowing that will confirm the pre‑stage behavior in your specific case.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 26 May 2024, 08:53 UTC
To build on the precedence rules mentioned, it is important to note how these layers interact when using the withEnv step inside a script block. While the stage-level environment directive overrides the top-level one, a withEnv block creates a third, most-immediate layer of precedence.
If a variable is defined in all three locations, the resolution order within the withEnv block is:
withEnv (Highest precedence)environment blockenvironment directive (Lowest precedence)This behavior is consistent across Jenkins 2.x versions because Declarative Pipelines internally wrap stages in withEnv calls. When you manually add another withEnv inside a step, you are effectively nesting environment maps. To verify this, you can nest these definitions and use sh 'echo $VAR' to confirm that the innermost scope always masks the outer scopes.