- Published on
Jenkins Declarative Pipeline when이 무시될 때
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Jenkins Declarative Pipeline을 쓰다 보면 when 조건을 걸어두었는데도 stage가 실행되어 버리는 경험을 하게 됩니다. 로그를 보면 조건이 false로 찍히거나, 아예 조건 평가 로그가 기대와 다르게 나타나기도 하죠. 이 문제는 단순히 “Jenkins 버그”로 치부하기엔 원인이 다양합니다. Declarative의 when은 평가 시점, 에이전트 할당 여부, 멀티브랜치/PR 이벤트의 변수 구성, checkout 여부, 환경변수/스크립트 사용 방식에 크게 영향을 받습니다.
이 글에서는 “when이 무시되는 것처럼 보이는” 대표 케이스를 재현 가능한 형태로 분류하고, 각 케이스별로 가장 안전한 해결 패턴을 제시합니다.
> 운영 환경에서 이런 이슈는 불필요한 빌드/배포 실행으로 이어집니다. 예를 들어 조건이 잘못 풀려 배포 단계가 실행되면 장애로 직결될 수 있으니, 원인을 정확히 잡아두는 게 중요합니다. (비슷한 맥락으로, 인프라에서 조건/상태 판단이 틀어져 장애가 커지는 사례는 리눅스 디스크 100%? inode 고갈 진단·복구 실전 같은 글에서도 반복적으로 나타납니다.)
문제 증상: when은 false인데 stage가 돈다?
Declarative에서 when은 “stage를 실행할지 말지”를 결정합니다. 그런데 현장에서 자주 보이는 증상은 다음 중 하나로 수렴합니다.
- 조건이 false인데도 stage steps가 실행됨
- stage는 스킵된 것 같은데 agent가 잡히고 workspace/checkout이 수행됨 (비용/시간 낭비)
- PR/브랜치 조건(
branch,changeRequest)이 기대와 다르게 매칭됨 environment에서 만든 변수로when을 걸었는데 항상 true/false로 고정됨
이때 “정말 when이 무시된 것인지”, 아니면 “stage는 스킵됐지만 agent/checkout은 선행된 것인지”부터 분리해서 봐야 합니다.
1) beforeAgent를 안 써서 “실행된 것처럼” 보이는 케이스
when은 기본적으로 stage 진입 전에 평가되지만, agent 할당/checkout 등의 동작이 먼저 일어나는 것처럼 보이는 상황이 발생할 수 있습니다. 특히 stage에 agent { ... }가 붙어 있고, 플러그인/설정에 따라 workspace 준비가 선행되면 “조건이 무시됐다”로 오해하기 쉽습니다.
해결: when { beforeAgent true }
beforeAgent true를 주면 agent를 잡기 전에 조건을 평가합니다.
pipeline {
agent none
stages {
stage('Deploy') {
agent { label 'linux' }
when {
beforeAgent true
branch 'main'
}
steps {
sh 'echo deploying...'
}
}
}
}
main이 아닐 때는 agent를 잡지도 않고 바로 스킵됩니다.- 비용이 큰 Kubernetes agent/EC2 ephemeral agent 환경에서 특히 효과가 큽니다.
2) when에서 쓰는 값이 “평가 시점에 아직 없음”
Declarative는 블록별로 평가 시점이 다릅니다. 대표적으로 다음 값들은 checkout 이후에야 확정되거나, 특정 플러그인 동작 이후에만 들어옵니다.
env.GIT_BRANCH,env.GIT_COMMIT등 SCM 관련 envcurrentBuild.changeSets기반 변경 파일- PR 관련 메타(플러그인/SCM 공급자에 따라 다름)
흔한 실수: environment에서 만든 변수로 when을 제어
예를 들어 아래처럼 environment에서 shell로 값을 만들고, 그걸 when에서 쓰면 기대와 달라질 수 있습니다.
pipeline {
agent any
environment {
// Declarative의 environment는 "단순 문자열/크리덴셜" 위주로 쓰는 게 안전
SHOULD_DEPLOY = "${env.BRANCH_NAME == 'main'}"
}
stages {
stage('Deploy') {
when {
expression { return env.SHOULD_DEPLOY == 'true' }
}
steps {
sh 'echo deploy'
}
}
}
}
위 코드는 상황에 따라 SHOULD_DEPLOY가 의도와 다르게 고정되거나, 문자열/불리언 변환 이슈로 항상 true처럼 보일 수 있습니다.
해결 패턴 A: when에서 직접 판단 (가장 단순)
stage('Deploy') {
when {
beforeAgent true
expression {
return env.BRANCH_NAME == 'main'
}
}
steps {
sh 'echo deploy'
}
}
해결 패턴 B: script에서 계산 후 when 대신 if로 감싸기
정말 복잡한 계산(파일 파싱, API 호출 등)이 필요하면 Declarative의 when에 무리하게 넣기보다, stage 자체는 실행하되 내부에서 제어하는 편이 안전할 때도 있습니다.
stage('Deploy') {
steps {
script {
def shouldDeploy = (env.BRANCH_NAME == 'main') && (env.DEPLOY == 'true')
if (!shouldDeploy) {
echo "skip deploy"
return
}
sh 'echo deploy'
}
}
}
단, 이 방식은 stage 자체가 실행되므로 “스킵”으로 남기고 싶거나 agent 비용을 줄이고 싶다면 when + beforeAgent가 더 낫습니다.
3) branch 조건이 PR 빌드에서 기대대로 동작하지 않음
Multibranch Pipeline에서 PR 빌드는 일반 브랜치 빌드와 컨텍스트가 다릅니다.
env.BRANCH_NAME는PR-123같은 형태가 될 수 있습니다.branch 'main'은 “현재 빌드 브랜치” 기준이라 PR 빌드에서는 false가 됩니다.- 그런데 “PR의 타겟이 main일 때만 실행” 같은 요구사항이라면
branch가 아니라changeRequest계열을 써야 합니다.
해결: PR 타겟 브랜치 기준이면 changeRequest(target: 'main')
stage('PR Checks') {
when {
beforeAgent true
changeRequest(target: 'main')
}
steps {
sh 'echo run checks for PR targeting main'
}
}
PR이 아닌 경우까지 포함해 “main 브랜치 또는 main 대상으로 들어오는 PR”
stage('Quality Gate') {
when {
beforeAgent true
anyOf {
branch 'main'
changeRequest(target: 'main')
}
}
steps {
sh 'echo quality gate'
}
}
이 조합을 쓰면 “PR에서도 main 브랜치에서도” 동일한 게이트를 안정적으로 걸 수 있습니다.
4) changeset/변경 파일 기반 when이 항상 매칭되는 것처럼 보임
when { changeset ... }는 변경 파일을 기반으로 stage 실행 여부를 결정합니다. 그런데 다음 조건이 충족되지 않으면 원하는 대로 동작하지 않거나, 반대로 너무 자주 true가 됩니다.
- 빌드가 첫 빌드라서 비교 대상이 없음
- SCM checkout 방식/플러그인에 따라 change log가 비어 있음
- merge commit/PR merge 전략에 따라 변경 파일이 예상과 다름
안전 패턴: changeset은 보조 조건으로 두고, 로그를 함께 출력
stage('Build Frontend') {
when {
beforeAgent true
anyOf {
changeset "frontend/**"
branch 'main' // main은 항상 빌드한다 같은 정책
}
}
steps {
sh 'echo build frontend'
}
}
변경 파일 기반 최적화를 걸 때는 “변경 감지가 실패하면 어떻게 할 것인가(보수적으로 항상 실행 vs 보수적으로 스킵)” 정책을 먼저 정하고 조건을 구성하는 게 좋습니다.
5) expression에서의 Groovy truthiness/문자열 비교 실수
when { expression { ... } }는 Groovy 표현식을 평가합니다. 여기서 문자열/널 처리 때문에 “항상 true”가 되는 실수가 많습니다.
대표 실수: 문자열 'false'는 truthy
when {
expression { return env.DEPLOY } // env.DEPLOY가 'false'여도 truthy
}
해결: 명시 비교
when {
beforeAgent true
expression { return env.DEPLOY?.toBoolean() }
}
toBoolean()은 'true'(대소문자 무관)에만 true를 주고 나머지는 false로 처리하므로 안전합니다.
6) 중첩 stage/병렬(parallel)에서 “스킵 로그”를 놓치는 경우
병렬 stage에서는 로그가 섞여 “조건이 무시됐다”로 오해하기 쉽습니다. 실제로는 한 브랜치만 실행되고 다른 브랜치는 스킵됐는데, 콘솔에서 스킵 메시지를 못 본 케이스가 많습니다.
권장: 조건을 로그로 남기는 디버그 stage 추가
문제 재현 시에는 짧게라도 “현재 컨텍스트”를 출력해두면 원인 파악이 빨라집니다.
stage('Debug Context') {
steps {
echo "BRANCH_NAME=${env.BRANCH_NAME}"
echo "CHANGE_ID=${env.CHANGE_ID}"
echo "CHANGE_TARGET=${env.CHANGE_TARGET}"
echo "GIT_BRANCH=${env.GIT_BRANCH}"
sh 'git rev-parse --abbrev-ref HEAD || true'
}
}
특히 PR/멀티브랜치에서 CHANGE_* 변수 유무는 when changeRequest() 동작을 이해하는 핵심 단서입니다.
7) Declarative 문법 혼용: stage 밖의 조건/동적 stage 생성
Declarative는 구조가 정적이어야 합니다. script 블록에서 stage를 동적으로 만들거나, 조건을 억지로 섞으면 UI나 실행 흐름이 “조건이 무시된 것처럼” 보일 수 있습니다.
권장: 동적 분기는 parallel/matrix/when 조합으로 표현
예를 들어 환경별 배포를 동적으로 만들고 싶다면 matrix가 더 선언적이고, 조건도 명확해집니다.
stage('Deploy Matrix') {
matrix {
axes {
axis {
name 'ENV'
values 'dev', 'prod'
}
}
stages {
stage('Deploy') {
when {
beforeAgent true
expression {
return (ENV == 'dev') || (ENV == 'prod' && env.BRANCH_NAME == 'main')
}
}
steps {
sh 'echo deploy to $ENV'
}
}
}
}
}
재현/점검 체크리스트
when이 무시된다고 느껴질 때 아래 순서로 확인하면 대부분 원인이 좁혀집니다.
- 정말 steps가 실행됐는지 확인 (agent 잡힘/checkout만 된 건 아닌지)
- stage에
agent가 있다면when { beforeAgent true }적용 - 멀티브랜치/PR이면
BRANCH_NAME,CHANGE_ID,CHANGE_TARGET로그 출력 - PR 타겟 기준 조건이면
branch대신changeRequest(target: ...) expression에서 문자열 truthiness 제거 (toBoolean(), 명시 비교)changeset기반이면 첫 빌드/merge 전략/checkout 방식 점검
이런 “조건 평가/컨텍스트 차이” 문제는 CI뿐 아니라 API/클라우드 트러블슈팅에서도 자주 등장합니다. 예를 들어 요청 파라미터가 특정 시점에 누락되어 오류가 나는 패턴은 OpenAI Responses API 400 invalid_request_error 원인과 해결 같은 사례와도 결이 비슷합니다.
결론
Jenkins Declarative Pipeline에서 when이 무시되는 것처럼 보일 때는, 실제로는 다음 중 하나인 경우가 대부분입니다.
- 조건 평가 전에 agent/checkout이 진행되어 “실행된 것처럼” 보임 →
beforeAgent true - PR/멀티브랜치에서 브랜치 컨텍스트가 다름 →
changeRequest(target: ...)활용 expression에서 문자열/널 처리 실수 →toBoolean()/명시 비교changeset비교 기반 조건의 한계(첫 빌드, merge 전략 등)
가장 안전한 기본값은 when에는 가능한 한 단순한 조건을, 그리고 비용이 큰 stage에는 beforeAgent true를 습관적으로 넣는 것입니다. 이렇게만 해도 “조건이 무시됐다”는 착시의 70~80%는 사라집니다.