<p>There's a few things to note. First is that in the absense of a shebang being supplied, the actual interpreter that will be used is actually &quot;sh&quot;, not &quot;bash&quot;.</p>
<p>Taking a look at the Jenkins source for the <a href="https://github.com/jenkinsci/durable-task-plugin/blob/master/src/main/java/org/jenkinsci/plugins/durabletask/BourneShellScript.java" rel="nofollow noreferrer">BourneShellScript task</a>, what happens is that Jenkins does not actually return the exit code from the subshell - it monitors a file called &quot;jenkins-result.txt for changes (using a <a href="https://github.com/jenkinsci/durable-task-plugin/blob/master/src/main/java/org/jenkinsci/plugins/durabletask/FileMonitoringTask.java" rel="nofollow noreferrer">FileMonitoringTask</a>) and when that file has been updated, it merely assigns the content of it as the exit status.</p>
<p>From what I can see, when you call &quot;sh&quot; it does not just run your script code, but actually wraps it in a fairly substantial block of boilerplate code (refer line 272 of the above linked BourneShellScript). In effect, something closer to this:</p>
<pre><code>sh -c ({ while [ -d '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0' -a \\! -f '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-result.txt' ]; do touch '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-log.txt'; sleep 3; done } &amp; jsc=durable-2fa119e0; JENKINS_SERVER_COOKIE=$jsc sh -xe '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/script.sh' &gt; '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-output.txt' 2&gt; 'jenkins-log.txt'; echo $? &gt; '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-result.txt.tmp'; mv '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-result.txt.tmp' '/home/user/workspace/newbie_dont_know_what_hes_doin\@tmp/durable-2fa119e0/jenkins-result.txt'; wait) &gt;&amp;- 2&gt;&amp;- &amp;
</code></pre>
<p>This is likely necessary to support scenarios where the node on which the script is executed is not the node on which the pipeline is executing, however it does leave a lot of room for error in returning the result, depending on the idiosyncracies of whatever interpreter &quot;sh&quot; is aliased to (not necessarily bash) and the OS on which it is running.</p>

CC BY-SA 4.0
2021-05-14T15:42:44.850Z
I agree with your assessment that this leaves a lot of room for error in returning the result. This raises the question, then: is there some other way to capture the true status code from a command? I'm still very much trying to get into the DevOps "headspace"...so I'm also trying to understand: what is that `sh(..., returnStatus:true)` meant to represent? I mean - your answer is clear: it's the return-code of the boilerplate wrapper...but why would a DevOps engineer want _that_ value, as opposed to the returnStatus of his actual command? (Which seems the more natural interpretation)
StoneThrow
CC BY-SA 4.0
2022-07-01T05:00:14.430Z
This Q and A is a great illustration of the minefield that is Jenkins+groovy (IMNSHO).
qneill
CC BY-SA 4.0
2021-05-14T06:25:36.517Z
2021-05-14T06:25:36.517Z
Kyanar
<p>This is my little newbie Groovy script, trying to cobble together a very beginner understanding of Jenkins Pipelines:</p>
<pre><code>node {
 stage(&quot;hello&quot;) {
  def var = &quot;val&quot;
  echo &quot;${var}&quot;
  def stdout = sh( script: 'pwd', returnStdout: true ).trim()
  echo &quot;${stdout}&quot;
  def ret_status = sh( script: 'cd subdir', returnStatus: true )
  echo &quot;${ret_status}&quot;
 }
}
</code></pre>
<p>The output is:</p>
<pre><code>Started by user unknown or anonymous
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /home/user/workspace/newbie_dont_know_what_hes_doin
[Pipeline] {
[Pipeline] stage
[Pipeline] { (hello)
[Pipeline] echo
val
[Pipeline] sh
+ pwd
[Pipeline] echo
/home/user/workspace/newbie_dont_know_what_hes_doin
[Pipeline] sh
+ cd subdir
/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/script.sh: 1: cd: can't cd to subdir
[Pipeline] echo
2
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
</code></pre>
<p>The failure to <code>cd subdir</code> is intentional -- to demonstrate the mechanics of capturing shell stdout and status.</p>
<p><strong>Why does the failed <code>cd subdir</code> report a returnStatus of <code>2</code> when <code>cd</code>ing to nonexistent directories in <code>bash</code> returns status-code <code>1</code>?</strong> The <a href="https://www.jenkins.io/doc/pipeline/steps/workflow-durable-task-step/#sh-shell-script" rel="nofollow noreferrer">Groovy documentation</a> I have as reference says <code>sh</code> runs a Bourne shell script.</p>
<pre><code>$ echo $SHELL
/bin/bash
$ cd subdir
-bash: cd: subdir: No such file or directory
$ echo $?
1
$
</code></pre>

<jenkins><jenkins-pipeline><groovy><bash>
Why does Jenkins Pipeline script returnStatus value not match status examined in shell?
StoneThrow
# Why does Jenkins Pipeline script returnStatus value not match status examined in shell?

(Asked by: username_0 on 2021-05-14)

This is my little newbie Groovy script, trying to cobble together a very beginner understanding of Jenkins Pipelines:
node {
 stage("hello") {
  def var = "val"
  echo "${var}"
  def stdout = sh( script: 'pwd', returnStdout: true ).trim()
  echo "${stdout}"
  def ret_status = sh( script: 'cd subdir', returnStatus: true )
  echo "${ret_status}"
 }
}

The output is:
Started by user unknown or anonymous
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /home/user/workspace/newbie_dont_know_what_hes_doin
[Pipeline] {
[Pipeline] stage
[Pipeline] { (hello)
[Pipeline] echo
val
[Pipeline] sh
+ pwd
[Pipeline] echo
/home/user/workspace/newbie_dont_know_what_hes_doin
[Pipeline] sh
+ cd subdir
/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/script.sh: 1: cd: can't cd to subdir
[Pipeline] echo
2
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

The failure to cd subdir is intentional -- to demonstrate the mechanics of capturing shell stdout and status.
Why does the failed cd subdir report a returnStatus of 2 when cding to nonexistent directories in bash returns status-code 1? The Groovy documentation I have as reference says sh runs a Bourne shell script.
$ echo $SHELL
/bin/bash
$ cd subdir
-bash: cd: subdir: No such file or directory
$ echo $?
1
$

**Tags:** jenkins, jenkins-pipeline, groovy, bash


---

## 1 answers

### Answer by username_1 on 2021-05-14. Score: 4 | Accepted answer

There's a few things to note. First is that in the absense of a shebang being supplied, the actual interpreter that will be used is actually "sh", not "bash".
Taking a look at the Jenkins source for the BourneShellScript task, what happens is that Jenkins does not actually return the exit code from the subshell - it monitors a file called "jenkins-result.txt for changes (using a FileMonitoringTask) and when that file has been updated, it merely assigns the content of it as the exit status.
From what I can see, when you call "sh" it does not just run your script code, but actually wraps it in a fairly substantial block of boilerplate code (refer line 272 of the above linked BourneShellScript). In effect, something closer to this:
sh -c ({ while [ -d '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0' -a \\! -f '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-result.txt' ]; do touch '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-log.txt'; sleep 3; done } & jsc=durable-2fa119e0; JENKINS_SERVER_COOKIE=$jsc sh -xe '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/script.sh' > '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-output.txt' 2> 'jenkins-log.txt'; echo $? > '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-result.txt.tmp'; mv '/home/user/workspace/newbie_dont_know_what_hes_doin@tmp/durable-2fa119e0/jenkins-result.txt.tmp' '/home/user/workspace/newbie_dont_know_what_hes_doin\@tmp/durable-2fa119e0/jenkins-result.txt'; wait) >&- 2>&- &

This is likely necessary to support scenarios where the node on which the script is executed is not the node on which the pipeline is executing, however it does leave a lot of room for error in returning the result, depending on the idiosyncracies of whatever interpreter "sh" is aliased to (not necessarily bash) and the OS on which it is running.

> **Comments:**
> **username_0**: I agree with your assessment that this leaves a lot of room for error in returning the result. This raises the question, then: is there some other way to capture the true status code from a command? I'm still very much trying to get into the DevOps "headspace"...so I'm also trying to understand: what is that `sh(..., returnStatus:true)` meant to represent? I mean - your answer is clear: it's the return-code of the boilerplate wrapper...but why would a DevOps engineer want _that_ value, as opposed to the returnStatus of his actual command? (Which seems the more natural interpretation)  
> **username_2**: This Q and A is a great illustration of the minefield that is Jenkins+groovy (IMNSHO).  

---

