개발기 #2] 10시간 동안 저를 괴롭힌 GitHub Actions 오류, 3가지 패턴으로 완벽 정복했습니다

반응형

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


🚩 실제 오류 로그 (당시 기록)

아래는 그날 저를 10시간 붙잡아 둔 실제 로그의 핵심 구간입니다.

당시 100번은 넘게 본 화면 이네요

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::로 단계별 묶음 → 원인 파악 속도 ↑

💡 교훈

  1. 오류 로그는 적이 아니라 친절한 길잡이입니다. 어디가 아픈지 정확히 알려주니까요.
  2. 셸과 툴의 기본 동작 원리를 5분만 더 투자해서 이해하면, 5시간의 삽질을 막을 수 있습니다.
  3. 무엇보다 가장 큰 교훈은, 이제 저는 어떤 오류를 만나도 '결국 해결할 수 있다'는 자신감을 얻었다는 것입니다.

다음 글 예고

다음 [개발기 #3]에서는 왜 제가 PDF 파싱을 버리고 Excel 기반으로 갈아탔는지,
실무 친화적인 기술 선택의 기준을 사례와 함께 정리해 드리겠습니다.

반응형