skip to content

A Jenkins pipeline builds target/app.jar in one stage, and a later stage that declares a different agent cannot find the file. Why, and how do you get the artifact onto the second agent?

level: seniorimportance: should knowfreq 52%

answer

  1. local disk, not shared storage
  2. fresh checkout has no build output
  3. stash goes through the controller
  4. small handoff versus real artifact store
  5. stale reuse is the mirror-image bug

basics

~20 s

Each Jenkins node has its own workspace directory and nothing is shared between them, so the second agent starts from a fresh checkout with no build output. Move the file explicitly with stash/unstash, archived artifacts, or an artifact repository.

solid answer

~50 s

Workspaces in Jenkins are per-node directories under the agent's remote root, not a shared filesystem. When a stage declares a different agent, Jenkins allocates an executor on that node, creates a workspace there and runs the implicit `checkout scm` — so the source is present but `target/app.jar` is not, because build output was never committed. The options, in increasing weight: `stash name: 'app', includes: 'target/*.jar'` in the producing stage and `unstash 'app'` in the consumer, which routes the files through the controller and discards them when the build ends — fine for tens of megabytes, wrong for gigabytes. `archiveArtifacts` stores them with the build so later builds and humans can fetch them, retrieved in another job with the Copy Artifact plugin. Best for anything you will actually deploy: publish once to a real artifact repository or container registry and have downstream stages pull that immutable coordinate, so you promote the same binary rather than rebuilding it.

code

groovy · 21 lines
groovy
pipeline {
  agent none
  stages {
    stage('Build') {
      agent { label 'linux' }
      steps {
        sh './gradlew assemble'
        stash name: 'app', includes: 'build/libs/*.jar'
        archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true
      }
      post { always { cleanWs() } }
    }
    stage('Deploy') {
      agent { label 'deploy' }
      steps {
        unstash 'app'
        sh 'ls -l build/libs'
      }
    }
  }
}

go deeper

for a junior

Know that each agent has its own workspace on its own disk and that files do not travel between stages on different agents. Name stash and unstash as the way to move them.

for a middle

Explain that a new stage agent brings a fresh workspace plus an implicit checkout scm, so source is present but build output is not, and that stash routes files through the controller and disappears when the build ends.

for a senior

Draw the boundary between stash, archived artifacts and a real repository, and connect it to build-once-and-promote so rollbacks never require the original workspace. Mention @2 workspaces and stale-workspace reuse as the mirror-image failure.

for a principal

Set the house rule on where build outputs live and how they are addressed — immutable coordinates or digests in a registry — so pipelines, retention policy and rollback strategy agree, and no deployment depends on an agent's disk still existing.

## Why the file is missing A Jenkins workspace is an ordinary directory on the agent's own disk, under that agent's configured remote root — something like `/var/jenkins/workspace/<job-name>`. It is local. There is no distributed filesystem behind it, no synchronisation between agents, and no implicit copying when a pipeline moves from one node to another. So when the pipeline does this: ```groovy stage('Build') { agent { label 'linux' } steps { sh './gradlew assemble' } } stage('Deploy') { agent { label 'deploy' } steps { sh 'ls target/app.jar' } } ``` the Deploy stage gets a *new* executor on a *different* machine, with a *new, empty* workspace, into which Jenkins performs the implicit `checkout scm`. The repository is there. `target/app.jar` is not — it is on the other agent, and it never existed in source control. The symptom is a confusing "No such file or directory" in a pipeline that visibly built the file thirty seconds earlier. The same class of surprise shows up in three neighbouring situations: - **Same label, different node.** `agent { label 'linux' }` twice can land on two different machines in the pool. Even reusing the same label guarantees nothing. - **Same node, concurrent builds.** If two builds of the same job run on one node at once, the second gets a suffixed workspace like `myjob@2`. Anything that hard-codes a workspace path breaks. - **Same node, later build.** A workspace *is* reused between builds on the same node, which produces the mirror-image bug: stale files from a previous build making the current one pass or fail spuriously. `deleteDir()` or the Workspace Cleanup plugin's `cleanWs()` in a `post` block is the usual answer, and ephemeral agents (a container or pod per build) make the whole class of problem disappear. ## Moving files between agents ### stash and unstash ```groovy stage('Build') { agent { label 'linux' } steps { sh './gradlew assemble' stash name: 'app', includes: 'build/libs/*.jar' } } stage('Deploy') { agent { label 'deploy' } steps { unstash 'app' sh './deploy.sh build/libs/app.jar' } } ``` `stash` takes an ant-style include/exclude pattern from the current workspace and stores the result **on the controller**, keyed by name for the duration of this build. `unstash` expands it into the current workspace, preserving relative paths. It is built for exactly this pipeline-internal handoff, and its constraints follow from where the data goes: everything crosses the controller's disk and network twice, stashes are deleted when the build finishes, and the Jenkins documentation is explicit that it is meant for small files — think tens of megabytes, not build caches or Docker images. Stashing a multi-gigabyte artifact is a reliable way to make the controller the bottleneck for every other job. ### archiveArtifacts ```groovy archiveArtifacts artifacts: 'build/libs/*.jar', fingerprint: true ``` Archiving attaches the files to the build record, so they survive the build, are downloadable from the UI, and can be fetched by another job with the Copy Artifact plugin's `copyArtifacts` step. `fingerprint: true` records a hash so Jenkins can later tell you which builds produced and consumed a given file. Archives are retained per your build-retention policy, which also means they are a real disk-consumption decision on the controller. ### A real artifact repository For anything you will deploy, neither of the above is the right long-term answer. Publish the binary once to an artifact repository or container registry, address it by an immutable coordinate (a version, or better a digest), and have every later stage and environment pull *that*. The Jenkins-specific benefit is that a rollback or a re-deploy no longer needs the original build's workspace, its stash, or the agent that produced it to still exist — which is precisely the failure you hit when a deploy stage tries to rebuild instead of promoting. ## Related workspace controls worth naming - `agent { node { label 'linux'; customWorkspace '/build/fixed-path' } }` pins the workspace directory — occasionally required by toolchains that cannot cope with generated paths, at the cost of losing the `@2` concurrency safety. - `ws('/some/dir') { … }` in scripted pipelines runs a block in a specified workspace on the current node. - `env.WORKSPACE` gives the current workspace path, `env.NODE_NAME` the node the step is running on — both useful for proving to yourself which machine a stage actually landed on. - Jenkins also creates sibling directories such as `<workspace>@tmp` for temporary step files; do not assume the workspace is the only thing on disk for your build. ## The judgment an interviewer is listening for The mechanical answer is "use stash". The senior answer adds the boundary: stash for small, build-internal handoffs; archive for things humans and other jobs need; a repository or registry for anything that gets deployed — because build-once-and-promote is what makes a rollback possible without a rebuild.

  • What are the practical limits of stash, and when should you stop using it?
    Stashed content is stored on the controller for the life of the build, so every stash and unstash crosses the controller's disk and network. It is intended for small files — tens of megabytes. Once you are moving build caches, container images or large distributions, you are making the controller the bottleneck for every job; publish to an artifact repository or registry instead.
  • Why does a second concurrent build of the same job get a workspace named with an @2 suffix?
    Two builds of one job can be assigned to the same node at once, and they cannot share one directory. Jenkins allocates `<job>@2`, `@3` and so on so each concurrent build has an isolated workspace. This breaks anything that hard-codes the workspace path or a `customWorkspace`, which is a common cause of one concurrent build clobbering another's output.
  • A stage passes only because of files left by the previous build on the same agent. How do you prevent that class of failure?
    Workspaces are reused between builds on a node, so stale output can mask a broken build or a missing generated file. Clean deliberately — `deleteDir()` at the start or `cleanWs()` in a `post { always }` block — and prefer ephemeral agents where each build gets a fresh container or pod, which removes the reuse entirely rather than relying on remembering to clean.

saying these in an interview costs you the question

  • Assuming agents share one network-mounted workspace
  • Stashing gigabyte artifacts through the controller
  • Rebuilding in the deploy stage instead of promoting
  • Believing checkout scm restores previous build output
  • Hard-coding workspace paths that break under concurrency

context