처음 GitHub Actions를 도입했을 때 저는 하루를 통째로 날렸습니다.
워크플로우 한 번이면 테스트와 빌드, 배포까지 자동으로 굴러갈 줄 알았지만 현실은 달랐습니다.
YAML 구문 오류 → 경로 인식 실패 → 권한 문제… 그중에서도 가장 저를 괴롭혔던 것은, 제가 수정한 파일들을 서버에 자동으로 옮겨주는 '파일 동기화(rsync)' 단계였습니다. 이 단계가 실패하면, 제가 아무리 코드를 열심히 고쳐도 실제 서비스에는 전혀 반영되지 않는 최악의 상황이 발생하기 때문입니다.
🚩 실제 오류 로그 (당시 기록)
아래는 그날 저를 10시간 붙잡아 둔 실제 로그의 핵심 구간입니다.

rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1338) [sender=3.2.7]
Include: src/dynamo_demo/**
rsync: [sender] link_stat "/home/runner/work/src/dynamo_demo/**" failed: No such file or directory (2)
building file list ... done
sent 29 bytes received 12 bytes 82.00 bytes/sec
total size is 0 speedup is 0.00
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1338) [sender=3.2.7]
Include: docs/showcase/**
building file list ... done
rsync: [sender] link_stat "/home/runner/work/docs/showcase/**" failed: No such file or directory (2)
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1338) [sender=3.2.7]
단순히 “폴더가 없다”는 내용으로만 보였지만, 본질은 셸 확장과 rsync의 패턴 처리를 제대로 이해하지 못한 것이었습니다.
❗문제의 본질
1) 컴퓨터가 내 말을 오해하기 시작했습니다 (** 확장 문제)
저는 컴퓨터에게 "src/dynamo_demo/ 폴더 안에 있는 모든 것"을 옮겨달라고 **라는 약속된 기호를 사용했습니다.
하지만 GitHub Actions의 컴퓨터는 이 기호를 '약속'으로 이해하지 못하고,
** 라는 글자 자체를 파일 이름으로 착각하고 있었습니다. 당연히 그런 파일은 없으니 오류를 뿜어냈던 것입니다."
2) 존재하지 않는 선택적 폴더를 강제로 동기화
- 저장소에 항상 있는 폴더가 아니라면, 없을 때는 건너뛰어야 합니다.
- 없는데도 포함시키면 rsync는
code 23으로 실패합니다.
✅ 해결 전략 (제가 정리한 3가지 안전패턴)
패턴 A — rsync의 --include/--exclude로 패턴을 다루기 (권장)
rsync -av --delete \ --include='src/dynamo_demo/**' \ --include='docs/showcase/**' \ --exclude='*' \ ./ "${{ env.DEPLOY_DIR }}"
- 장점:
**확장을 셸에 의존하지 않고 rsync가 직접 처리합니다. - 포인트: 마지막
--exclude='*'로 나머지는 모두 제외 → 지정한 경로만 복사됩니다.
패턴 B — bash에서 globstar를 켠 뒤, 존재하는 경로만 rsync
# Actions step (bash)
shopt -s globstar nullglob
for p in src/dynamo_demo/** docs/showcase/**; do
# 존재하는 경로만 추가
[[ -e "$p" ]] && rsync -av "$p" "${{ env.DEPLOY_DIR }}/" || echo "skip: $p"
done
- 포인트:
nullglob를 켜서 매칭 없을 때 패턴 자체가 사라지게 만듭니다. - 장점: 선택 폴더가 없어도 실패하지 않습니다.
패턴 C — rsync 3.1+의 --ignore-missing-args 사용 (간단)
rsync -av --ignore-missing-args \ src/dynamo_demo/** docs/showcase/** \ "${{ env.DEPLOY_DIR }}/"
- 가장 간단한 회피책입니다. 다만 셸의 `` 확장** 문제는 여전히 신경 써야 합니다(패턴 B와 병행 추천).
🧩 제 워크플로우에서의 최종 형태 (발췌)
name: Deploy Docs
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Prepare deploy dir
run: |
mkdir -p dist
- name: Sync selected folders safely
shell: bash
run: |
set -e
shopt -s globstar nullglob
DEST=dist
include_list=(
"src/dynamo_demo/**"
"docs/showcase/**"
)
for pat in "${include_list[@]}"; do
for path in $pat; do
[[ -e "$path" ]] || { echo "skip: $pat"; continue; }
echo "::group::sync $path"
rsync -av "$path" "$DEST/"
echo "::endgroup::"
done
done
- 실패하지 않는 안전한 동기화를 기본값으로 만들었습니다.
- 선택 폴더가 없어도
skip:로그만 남기고 계속 진행합니다.
📊 효과
- 초기 세팅 시간: 하루 → 10분
- rsync 실패율: code 23 → 0건
- 로그 가독성:
::group::로 단계별 묶음 → 원인 파악 속도 ↑
💡 교훈
- 오류 로그는 적이 아니라 친절한 길잡이입니다. 어디가 아픈지 정확히 알려주니까요.
- 셸과 툴의 기본 동작 원리를 5분만 더 투자해서 이해하면, 5시간의 삽질을 막을 수 있습니다.
- 무엇보다 가장 큰 교훈은, 이제 저는 어떤 오류를 만나도 '결국 해결할 수 있다'는 자신감을 얻었다는 것입니다.
다음 글 예고
다음 [개발기 #3]에서는 왜 제가 PDF 파싱을 버리고 Excel 기반으로 갈아탔는지,
실무 친화적인 기술 선택의 기준을 사례와 함께 정리해 드리겠습니다.
'Project_Aegis > [Learning Log]' 카테고리의 다른 글
| [학습 일지 #002] 20년차 설계자가 코딩 문제 앞에서 머리가 하얘질 때 (1) | 2025.09.17 |
|---|---|
| [학습 일지 #001] GitHub Actions 동기화 오류, 10시간의 사투 끝에 얻은 교훈 (0) | 2025.09.03 |