Summary#
GitHub Actions reusable workflow(workflow_call)는 CI/CD 표준화에 유용하지만, 호출자(caller)와 호출 대상(called workflow) 사이의 경계가 명확하지 않으면 보안·배포·디버깅 실패가 발생하기 쉽다. 특히 secrets: inherit, permissions 권한 축소, matrix reusable workflow의 output 수집 방식, concurrency.cancel-in-progress의 취소 동작은 겉보기 YAML은 정상이어도 운영 환경에서 “왜 값이 비었는지”, “왜 토큰 권한이 부족한지”, “왜 배포가 취소됐는지”를 추적하기 어렵게 만든다.
핵심 운영 원칙은 다음과 같다.
- reusable workflow는 권한을 “상향”시킬 수 없고, 호출 체인에서 권한은 유지되거나 더 제한된다.
secrets: inherit는 편하지만 비밀값 전달 범위를 넓히므로, 신뢰 경계가 다른 workflow에는 명시적 secret 매핑이 더 안전하다.- matrix로 호출된 reusable workflow의 workflow-level output은 모든 matrix job 결과를 배열처럼 자동 fan-in하지 않는다.
concurrency는 같은 그룹의 pending/running job 또는 workflow run을 취소·대체할 수 있으므로, 배포 workflow에서는 group key 설계가 안전성에 직접 연결된다.
Key Points#
1. workflow_call secrets 전달 실패 모드#
Reusable workflow는 on.workflow_call로 정의되고, caller workflow에서 uses:로 호출된다. 이때 secret은 자동으로 모두 전달되지 않는다. 일반적으로는 caller가 called workflow에 전달할 secret을 명시해야 하며, 같은 organization 또는 enterprise 내부 reusable workflow에는 secrets: inherit를 사용할 수 있다.
실패 모드:
- called workflow에서 secret이 비어 있음
- caller가
secrets:매핑을 빠뜨렸거나, - called workflow의
on.workflow_call.secrets정의와 caller의 secret 이름이 맞지 않거나, -
secrets: inherit가 적용되지 않는 경계에서 사용됐을 수 있다. -
환경 secret(environment secret) 오해
- GitHub 문서에 따르면
on.workflow_call은environment키워드를 지원하지 않으며, reusable workflow 안의 job에서environment를 지정하면 caller에서 전달한 secret이 아니라 해당 environment의 secret이 사용될 수 있다. -
따라서 deployment environment secret과 reusable workflow input/secret을 혼합하면 예상과 다른 secret이 선택될 수 있다.
-
pull_request_target와 secret 노출 위험 pull_request_target는 fork에서 온 pull request에도 base repository context에서 실행될 수 있어, checkout 대상과 실행 코드가 안전하게 통제되지 않으면 secret 노출 위험이 커진다.- reusable workflow 자체가 안전하더라도, caller가 untrusted code를 checkout하거나 실행하면 secret 전달 정책이 우회적으로 위험해질 수 있다.
권장 패턴:
jobs:
call-deploy:
uses: org/repo/.github/workflows/deploy.yml@v1
secrets:
deploy_token: ${{ secrets.PROD_DEPLOY_TOKEN }}
secrets: inherit는 내부 신뢰 경계가 명확하고 called workflow가 필요한 secret만 참조한다는 확신이 있을 때 제한적으로 사용한다.
2. permissions 권한 축소와 token capability 오해#
Reusable workflow에서 자주 발생하는 장애는 GITHUB_TOKEN 권한 부족이다. GitHub Actions의 permissions는 workflow 또는 job 단위로 설정할 수 있으며, reusable workflow 호출 체인에서는 권한을 상승시킬 수 없고 유지하거나 더 제한할 수 있다.
실패 모드:
- called workflow가
contents: write,packages: write,id-token: write등을 요구하지만 실패 -
caller workflow의 top-level 또는 job-level
permissions가 더 낮게 설정되어 있으면 called workflow가 더 높은 권한을 선언해도 실제 권한은 부족할 수 있다. -
OIDC 배포 실패
-
cloud federation 배포에서
id-token: write가 필요한데 caller 쪽에서 빠뜨리면 called workflow 내부의 인증 단계가 실패한다. -
보안 강화를 위해 caller에서
permissions: read-all또는{}를 설정한 뒤 reusable workflow가 깨짐 - 중앙 reusable workflow가 release, tag, package publish, deployment status update를 수행한다면 caller의 최소 권한 정책과 충돌한다.
권장 패턴:
permissions:
contents: read
id-token: write
jobs:
deploy:
uses: org/platform/.github/workflows/deploy.yml@v1
reusable workflow 문서에는 “필요 권한 계약”을 명시해야 한다.
예:
Required caller permissions:
- contents: read
- id-token: write
- deployments: write, if GitHub deployments are updated
3. Matrix reusable workflow outputs: fan-out/fan-in 착각#
Reusable workflow는 on.workflow_call.outputs로 workflow output을 정의할 수 있다. 하지만 matrix strategy로 reusable workflow를 여러 번 호출할 때 output 처리는 일반적인 배열 fan-in처럼 동작하지 않는다.
GitHub 문서에 따르면 matrix로 실행된 reusable workflow가 output을 설정하면, reusable workflow output은 성공적으로 완료된 matrix 실행 중 실제 값을 설정한 마지막 완료 workflow의 값이 된다. 마지막 완료 workflow가 빈 문자열을 설정하면, 그 이전에 값을 설정한 workflow의 output이 사용될 수 있다.
실패 모드:
- matrix 결과 전체가 필요한데 output 하나만 남음
-
예:
service=a,b,c별 deploy URL을 모두 모으고 싶지만needs.deploy.outputs.url에는 하나의 값만 남는다. -
완료 순서에 따라 output이 바뀌는 것처럼 보임
-
matrix job 완료 순서는 고정되지 않으므로, “마지막 값” 의존은 비결정적으로 보일 수 있다.
-
빈 output과 누락 output 디버깅 어려움
- called workflow 내부 step output → job output → workflow output 연결 중 하나라도 빠지면 caller의
needscontext에서는 빈 값처럼 보일 수 있다.
비권장 예:
jobs:
deploy:
strategy:
matrix:
service: [api, web, worker]
uses: org/repo/.github/workflows/deploy-service.yml@v1
with:
service: ${{ matrix.service }}
summarize:
runs-on: ubuntu-latest
needs: deploy
steps:
- run: echo "${{ needs.deploy.outputs.url }}"
위 구조는 api, web, worker의 URL 전체 목록을 안정적으로 제공하지 않는다.
대안:
- matrix job별 artifact 업로드 후 별도 aggregation job에서 다운로드
- 외부 상태 저장소에 service별 결과 기록
- reusable workflow 대신 caller workflow 안에서 matrix job을 직접 정의하고 aggregation job을 명시
- JSON 파일을 artifact로 모아 fan-in 처리
4. concurrency.cancel-in-progress와 배포 취소/race condition#
GitHub Actions concurrency는 같은 concurrency group에 대해 동시에 실행되는 workflow 또는 job을 제한한다. 같은 group에는 running 1개와 pending 1개만 존재할 수 있으며, 새 run이 들어오면 기존 pending run은 취소된다. cancel-in-progress: true를 사용하면 running run도 취소할 수 있다.
실패 모드:
- 배포 중 새 push가 들어와 기존 배포가 취소됨
-
blue/green, migration, rollback-sensitive deployment에서는 중간 취소가 위험할 수 있다.
-
reusable workflow 내부와 caller 양쪽에서 concurrency를 설정해 의도치 않은 직렬화 발생
-
caller는 branch 단위 group을 쓰고, called workflow는 environment 단위 group을 쓰는 경우 서로 다른 레벨의 lock이 겹쳐 디버깅이 어려워질 수 있다.
-
group key가 너무 넓음
-
예:
production하나로 모든 서비스 배포를 묶으면 서로 독립적인 서비스 배포도 취소 또는 대기된다. -
group key가 너무 좁음
- 예: commit SHA를 group에 넣으면 사실상 concurrency 보호가 사라진다.
위험한 예:
concurrency:
group: production
cancel-in-progress: true
더 안전한 예:
concurrency:
group: deploy-${{ github.ref_name }}-${{ inputs.service }}-${{ inputs.environment }}
cancel-in-progress: false
또는 PR 검증처럼 최신 run만 의미 있는 경우에는 다음이 적절할 수 있다.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
배포에서는 “최신 run만 남기는 것”이 항상 안전하지 않다. 특히 database migration, external lock, partial rollout, deployment approval이 있는 workflow에서는 취소 가능 지점을 명시적으로 설계해야 한다.
5. Debugging checklist#
Reusable workflow 장애를 조사할 때는 다음 순서가 유용하다.
-
caller job의
uses:대상 확인 - branch/tag/SHA가 기대한 reusable workflow 버전인지 확인한다. - 보안상 mutable branch보다 tag 또는 SHA pinning을 고려한다. -
withinput과secrets매핑 확인 - called workflow의on.workflow_call.inputs- called workflow의on.workflow_call.secrets- caller의with:/secrets:이름 일치 여부 -
권한 확인 - caller top-level
permissions- caller job-levelpermissions- called workflow job-levelpermissions- 필요한 권한이 caller에서 이미 차단되지 않았는지 확인 -
output chain 확인 - step output - job output - workflow output - caller
needs.<job_id>.outputs.<name> -
matrix output 여부 확인 - 여러 matrix 결과를 하나의 workflow output으로 모으려는 설계인지 확인 - 필요하면 artifact 기반 aggregation으로 전환
-
concurrency group 확인 - caller와 called workflow 양쪽의
concurrency- group key의 범위 -cancel-in-progress조건 - deployment environment protection rule과의 상호작용
Cautions#
- 이 초안은 공개 GitHub Docs 중심으로 정리한 것이다. 실제 동작은 GitHub Enterprise Server 버전, repository visibility, organization policy, environment protection rule, fork 정책에 따라 달라질 수 있다.
secrets: inherit의 적용 범위와 제한은 GitHub 제품 업데이트에 따라 바뀔 수 있으므로, 운영 전 현재 문서를 확인해야 한다.pull_request_target관련 위험은 reusable workflow 자체의 문제가 아니라, untrusted pull request code를 어떤 권한과 secret context에서 실행하는지의 문제다.- matrix reusable workflow output은 “전체 결과 수집” 용도로 설계된 기능이 아니므로, 결과 목록이 필요하면 artifact 또는 별도 aggregation 단계를 설계하는 편이 안전하다.
concurrency.cancel-in-progress: true는 CI 검증에는 유용하지만, production deployment에는 위험할 수 있다. 취소되어도 안전한 단계와 취소되면 안 되는 단계를 구분해야 한다.- 공개 문서만으로는 모든 race condition 사례를 재현할 수 없다. 조직별 reusable workflow wrapper, deployment tool, cloud provider 인증 방식, custom action 구현에 따라 추가 실패 모드가 생길 수 있다.
Sources#
- https://docs.github.com/en/actions/using-workflows/reusing-workflows
- https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions
- https://docs.github.com/en/actions/learn-github-actions/contexts
- https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/control-workflow-concurrency
- https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions
- https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs
Related#
- Go 실전 패턴 Capsule
- Collector Incremental Polling Failure Modes: Conditional Requests, Dedupe, and Watermarks
- Collector Pipeline Failure Modes: Recrawl Scheduling, Deduplication, and Zero-Yield Extraction
Sagwan Revalidation 2026-06-15T03:27:08Z#
- verdict:
ok - note: GitHub Actions reusable workflow 주요 실패 모드 설명이 현재 문서와 부합함