GitHub Agentic Workflows: 마크다운으로 쓰는 AI 저장소 자동화

GitHub Agentic Workflows: 마크다운으로 쓰는 AI 저장소 자동화

GitHub Actions로 저장소를 자동화하다 보면 어느 순간 벽에 부딪힙니다. 새 이슈가 열릴 때 알맞은 라벨을 붙여주고 싶은데, 그 규칙을 조건문으로 적으려니 답이 안 나오는 거죠. 제목에 “버그”가 들어가면 bug 라벨을 붙이자고 시작하지만, 정작 사용자가 올리는 이슈는 “로그인하면 화면이 하얘져요” 같은 문장이거든요. 😅

이런 일은 규칙이 아니라 판단이 필요한 작업입니다. 이슈를 읽고, 무슨 말인지 이해하고, 빠진 정보가 무엇인지 알아채야 하니까요. 사람이 하기엔 지루하고 기계가 하기엔 애매한, 딱 그 사이에 있는 일이죠.

GitHub이 내놓은 GitHub Agentic Workflows(이하 gh-aw)는 바로 이 틈을 노립니다. 워크플로우를 YAML이 아니라 마크다운으로 쓰고, “무엇을 해야 하는지”를 자연어로 적으면 AI 에이전트가 GitHub Actions 러너 안에서 그 지시를 수행합니다. 이번 글에서는 gh-aw가 어떻게 작동하는지, 마크다운 한 장이 어떤 워크플로우로 변신하는지, AI에게 저장소를 맡길 때 무엇을 조심해야 하는지 직접 돌려보면서 알아보겠습니다.

GitHub Agentic Workflows란?

gh-aw는 GitHub CLI 확장(extension) 형태로 배포되는 워크플로우 컴파일러입니다. .github/workflows/ 디렉토리에 마크다운 파일을 두면, 이를 GitHub Actions가 이해하는 YAML로 변환해주죠. MIT 라이선스로 공개된 저장소는 2025년 8월에 만들어졌습니다.

마크다운 파일은 두 부분으로 나뉩니다. 위쪽 YAML 프론트매터(front matter)에는 언제 실행할지, 어떤 권한을 줄지, 무엇을 쓸 수 있는지를 선언합니다. 아래쪽 본문에는 에이전트가 할 일을 사람이 읽는 문장으로 적고요.

---
on:
  issues:
    types: [opened]
permissions: read-all
safe-outputs:
  add-comment:
---

# 이슈 정리 도우미

방금 열린 이슈를 읽고, 내용이 불분명하면 어떤 정보가 더 필요한지 물어보세요.

프론트매터는 익숙한 GitHub Actions 문법 그대로입니다. 새로운 건 세이프 아웃풋(safe outputs)인데요, safe-outputs 항목에 에이전트가 할 수 있는 쓰기 작업을 미리 못 박아둡니다. 뒤에서 자세히 다루겠지만, 이 한 줄이 gh-aw 보안 모델의 핵심입니다.

본문은 코딩 에이전트에게 전달되는 프롬프트입니다. 어떤 엔진을 쓸지는 선택할 수 있어서, GitHub Copilot CLI가 기본이고 클로드 코드, OpenAI Codex, Google Gemini CLI도 붙일 수 있습니다.

설치하고 첫 워크플로우 만들기

gh-aw는 확장이라 설치가 한 줄로 끝납니다. GitHub CLI 2.0.0 이상이 깔려 있어야 합니다.

gh extension install github/gh-aw

설치가 끝나면 버전을 확인해봅니다.

gh aw version
결과
gh aw version v0.88.2

이제 저장소 루트에서 워크플로우를 하나 만들어보죠. gh aw new 명령은 주석이 잔뜩 달린 뼈대 파일을 만들어줍니다.

gh aw new issue-triage
결과
✓ Created new workflow: /path/to/repo/.github/workflows/issue-triage.md
i Edit the file to customize your workflow, then run 'gh aw compile' to generate the GitHub Actions workflow

생성된 파일을 열어보면 재미있습니다. safe-outputs 아래에 쓸 수 있는 출력 종류가 100개 가까이 주석으로 나열되어 있거든요. create-pull-request, add-labels, create-code-scanning-alert부터 jira-create-issue, linear-add-comment 같은 외부 서비스 연동까지 있습니다. 필요한 줄의 주석만 풀면 되니 문서를 뒤질 일이 줄어들죠.

처음부터 직접 쓰기 부담스럽다면 갤러리에 준비된 워크플로우를 가져다 쓸 수도 있습니다. 이슈 분류, PR 리뷰, CI 실패 조사, 문서 갱신처럼 흔한 작업은 이미 만들어져 있어요.

gh aw create issue-triage
gh aw create pr-review
gh aw create ci-failure

마크다운 워크플로우 뜯어보기

뼈대에서 주석을 걷어내고 실제로 쓸 만한 형태로 다듬어보겠습니다. 새 이슈가 열리면 내용을 읽고 라벨을 붙인 뒤, 빠진 정보를 되묻는 워크플로우입니다.

.github/workflows/issue-triage.md
---
on:
  issues:
    types: [opened]

permissions:
  contents: read
  issues: read

network: defaults

safe-outputs:
  add-comment:
  add-labels:
    allowed: [bug, feature, docs, question]
    max: 3
---

# 이슈 분류

새로 열린 이슈를 읽고 다음을 수행하세요.

1. 이슈 본문에서 재현 절차와 기대 동작이 적혀 있는지 확인합니다.
2. 내용에 맞는 라벨을 최대 3개까지 붙입니다.
3. 빠진 정보가 있으면 무엇이 더 필요한지 정중하게 댓글로 물어봅니다.

25줄이 전부입니다. 여기서 눈여겨볼 부분은 permissions에 쓰기 권한이 하나도 없다는 점입니다. 라벨을 붙이고 댓글을 다는 건 분명히 쓰기 작업인데도 말이죠. 이 모순은 잠시 뒤에 풀립니다.

network: defaults는 에이전트가 나갈 수 있는 바깥 주소를 GitHub과 인증서 서버 정도로 제한하겠다는 뜻입니다. 파이썬 패키지를 받아야 한다면 python, npm이 필요하면 node 같은 생태계 이름을 추가하거나 도메인을 직접 적습니다.

network:
  allowed:
    - defaults
    - node
    - "api.example.com"

프론트매터에서 조절할 수 있는 항목은 이 밖에도 많습니다. 실행 시간을 묶는 timeout-minutes, 대화 반복 횟수를 제한하는 max-turns, 다른 워크플로우 조각을 끌어오는 imports, MCP 서버를 붙이는 mcp-servers, 운영용 검증을 강화하는 strict 같은 것들이죠.

컴파일하면 무슨 일이 벌어질까요?

이제 컴파일할 차례입니다.

gh aw compile
결과
✓ .github/workflows/issue-triage.md (118.2 KB)
✓ Compiled 1 workflow: 1 succeeded, 0 warnings

118.2 KB라는 숫자가 눈에 띄지 않나요? 방금 쓴 마크다운은 1 KB도 안 되는데 말이죠. 실제로 생성된 issue-triage.lock.yml은 1,830줄입니다. 25줄이 1,830줄이 됐으니 대략 73배로 부푼 거예요.

이 .lock.yml 파일이 GitHub Actions가 실제로 실행하는 워크플로우입니다. 이름이 잠금 파일인 이유도 package-lock.json과 같습니다. 사람이 손댈 파일이 아니라 컴파일 결과물이니, 마크다운과 함께 커밋해두고 원본을 고칠 때마다 다시 컴파일하면 됩니다.

그럼 그 1,800줄에는 뭐가 들어 있을까요? 잡(job) 정의를 뽑아보면 여섯 개가 나옵니다.

flowchart TB
    accTitle: gh-aw가 컴파일한 워크플로우의 잡 실행 순서
    accDescr: 사전 활성화 잡이 권한과 한도를 확인하고, 활성화 잡이 이벤트 텍스트를 정제한 뒤, 읽기 전용 에이전트 잡이 실행된다. 에이전트가 남긴 출력 파일은 탐지 잡이 검사하며, 위협이 없다고 판정될 때만 쓰기 권한을 가진 세이프 아웃풋 잡이 실제 댓글과 라벨 작업을 수행한다.

    pre["pre_activation<br/>권한과 한도 확인"] --> act["activation<br/>이벤트 텍스트 정제"]
    act --> agent["agent<br/>읽기 전용으로 AI 실행"]
    agent -- "agent_output.json" --> det{"detection<br/>위협이 있는가?"}
    det -- "아니요" --> safe["safe_outputs<br/>실제 쓰기 수행"]
    det -- "예" --> block(["중단"])
    safe --> con(["conclusion<br/>결과 요약"])

    classDef readonly stroke:#2563eb,stroke-width:2px
    classDef write stroke:#dc2626,stroke-width:2px
    class agent,det readonly
    class safe write

여기에 더해 컴파일러는 워크플로우가 쓰는 액션을 전부 커밋 SHA로 고정합니다. 생성된 파일에는 actions/checkout v7.0.1이 3d3c42e5... 같은 해시로 박혀 있고, 방화벽과 MCP 게이트웨이 컨테이너 이미지도 다이제스트까지 고정됩니다. 태그는 옮겨 달 수 있지만 해시는 못 바꾸니, GitHub Actions를 안전하게 쓰는 방법에서 권장하는 방식을 컴파일러가 알아서 지켜줍니다.

컴파일 과정에는 actionlint, zizmor, poutine 세 가지 보안 검사기도 끼어듭니다. 권한 상승이나 공급망 위험이 감지되면 아예 컴파일이 실패하고요.

세이프 아웃풋: 에이전트에게 쓰기 권한을 주지 않기

앞서 미뤄둔 모순으로 돌아가 봅시다. 마크다운에는 읽기 권한만 적었는데 어떻게 라벨을 붙일까요? 컴파일된 잡의 권한을 나란히 보면 답이 나옵니다.

issue-triage.lock.yml (발췌)
agent:
  needs: activation
  permissions:
    contents: read
    issues: read

safe_outputs:
  needs: [activation, agent, detection]
  if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
  permissions:
    issues: write
    pull-requests: write

AI가 실행되는 agent 잡에는 읽기 권한만 있습니다. 에이전트는 라벨을 직접 붙이는 대신 “이런 라벨을 붙여줘”라는 요청을 JSON으로 적어 파일에 남길 뿐이에요.

{ "type": "add_labels", "labels": ["bug"] }

이 파일은 아티팩트로 업로드되고, 뒤이어 실행되는 safe_outputs 잡이 내용을 검증한 다음 GitHub API를 호출합니다. 쓰기 권한은 이 잡에만 있고, 이 잡은 AI가 아니라 정해진 스크립트로 돌아갑니다.

효과는 분명합니다. 프롬프트 인젝션(prompt injection)으로 에이전트가 완전히 넘어가더라도, 공격자가 얻는 건 읽기 전용 토큰뿐입니다. add-labels에 allowed: [bug, feature, docs, question]을 적어뒀다면 그 밖의 라벨은 애초에 통과하지 못하고요. GitHub Actions 권한 설정에서 이야기하는 최소 권한 원칙을 사람의 주의력이 아니라 워크플로우 구조로 강제한 것이죠.

쓸 수 있는 출력 종류는 상당히 넓습니다. 이슈와 PR을 만들고 닫는 것부터 리뷰 코멘트 작성, 라벨과 마일스톤 관리, 프로젝트 보드 갱신, SARIF 형식의 코드 스캐닝 경고 등록까지 있고, Jira나 Linear 같은 외부 이슈 트래커도 연결됩니다. 각 항목마다 max 값으로 한 번의 실행에서 만들 수 있는 개수를 묶어두니, 에이전트가 폭주해도 이슈 100개가 쏟아지는 사고는 나지 않습니다.

처음 도입할 때는 staged 모드가 유용합니다. 실제 API를 호출하는 대신 무엇을 하려 했는지 잡 요약에 미리보기로만 남기거든요.

safe-outputs:
  add-comment:
    staged: true

비슷한 목적으로 gh aw trial 명령도 있습니다. 가상의 저장소를 상대로 워크플로우를 시험 운전해서, 진짜 저장소에 손대기 전에 결과를 확인할 수 있어요.

프롬프트 인젝션을 막는 장치들

세이프 아웃풋이 피해 범위를 좁히는 장치라면, 애초에 에이전트가 속아 넘어가지 않게 막는 장치도 따로 있습니다.

우선 이슈나 댓글 본문은 에이전트에게 전달되기 전에 정제를 거칩니다. @사용자 표기는 백틱으로 감싸 알림이 튀지 않게 하고, fixes #123 같은 문구도 무력화해 엉뚱한 이슈가 닫히는 일을 막습니다. <script> 같은 태그는 괄호 형태로 바꾸고, 허용 목록에 없는 도메인의 URL은 (redacted)로 지웁니다. 유니코드 동형 문자를 정규화하고 ANSI 이스케이프 문자를 제거하는 단계도 있고요.

무결성 필터(integrity filter)는 한 걸음 더 나갑니다. 공개 저장소라면 기본으로 approved 수준이 적용되어, 저장소 소유자나 멤버, 협업자가 쓴 내용만 에이전트에게 전달됩니다. 처음 보는 계정이 이슈에 심어둔 지시문은 에이전트 눈에 닿지도 않는 거죠. 워크플로우를 띄울 수 있는 사람도 roles로 따로 제한합니다.

on:
  issues:
    types: [opened]
  roles: [admin, maintainer, write]

tools:
  github:
    min-integrity: approved

네트워크 쪽도 막혀 있습니다. 에이전트는 컨테이너 안에서 실행되고, 바깥으로 나가는 HTTP 트래픽은 iptables 규칙으로 Squid 프록시를 거치게 됩니다. 허용 목록에 없는 도메인은 여기서 차단되니, 저장소 내용을 외부로 빼돌리는 통로가 좁아집니다. 러너 환경에 따라 gVisor나 KVM 기반 마이크로 VM으로 격리 수준을 더 올릴 수도 있어요.

GitHub MCP 서버를 붙일 때도 도구가 통째로 열리지 않습니다. 앞서 컴파일한 워크플로우를 보면 에이전트에게 노출된 GitHub 도구는 issue_read, list_issues, search_code 같은 읽기 작업뿐이고, 쓰기 도구는 목록에 없습니다.

마지막 관문은 detection 잡입니다. 에이전트가 남긴 출력과 코드 변경 패치를 별도의 AI가 읽고 시크릿 유출이나 악성 패치가 없는지 살피는데요, 위협으로 판정되면 세이프 아웃풋 잡 전체가 막힙니다. 여기에 TruffleHog나 Semgrep 같은 스캐너를 직접 끼워 넣을 수도 있습니다.

safe-outputs:
  create-issue:
  threat-detection:
    steps:
      - name: Run TruffleHog
        run: trufflehog filesystem /tmp/gh-aw --only-verified

트리거: 슬래시 명령과 느슨한 스케줄

GitHub Actions 트리거에서 쓰던 이벤트는 그대로 쓸 수 있고, gh-aw는 여기에 몇 가지를 더 얹었습니다.

가장 쓸모 있는 건 슬래시 명령입니다. 이슈나 PR 댓글에 /investigate라고 적으면 워크플로우가 깨어납니다.

on:
  slash_command:
    name: investigate
    events: [issues, issue_comment]
  reaction: eyes

reaction을 함께 적어두면 명령을 접수했다는 뜻으로 해당 댓글에 👀 이모지가 달립니다. 실행 중인지 아닌지 사람이 알 수 있게 해주는 작은 배려죠.

주기 실행도 조금 다릅니다. crontab 표현식 대신 사람 말에 가까운 문법을 쓸 수 있어요. weekly on friday around 5pm이나 daily between 9:00 and 17:00, every 10 minutes 같은 표현이 모두 통합니다.

on:
  schedule: daily around 14:00

이걸 컴파일하면 어떤 cron이 나올까요? 정확히 14시가 아닙니다.

lock.yml (발췌)
schedule:
  - cron: "31 13 * * *" # Friendly format: daily around 14:00 (scattered)

컴파일러는 파일 경로를 바탕으로 각 워크플로우에 조금씩 다른 실행 시각을 배정합니다. 조직 전체가 정각에 몰려 러너를 잡아먹는 일을 피하려는 설계인데, 같은 파일에서는 늘 같은 시각이 나오도록 결정론적으로 계산하니 예측도 가능합니다.

비용이 걱정되는 실험 단계라면 stop-after로 유효 기간을 걸어둘 수 있습니다. 켜둔 걸 잊어버려도 알아서 멈추니까요.

on:
  schedule: weekly on monday
  stop-after: "+7d"

엔진과 비용 관리

엔진은 다섯 가지 중에서 고릅니다. 아무것도 적지 않으면 Copilot이 쓰이고, engine 한 줄로 바꿀 수 있습니다.

엔진engine 값인증
GitHub Copilotcopilotcopilot-requests: write 또는 COPILOT_GITHUB_TOKEN
Claude CodeclaudeANTHROPIC_API_KEY
OpenAI CodexcodexCODEX_API_KEY 또는 OPENAI_API_KEY
Google Gemini CLIgeminiGEMINI_API_KEY
PipiCopilot 인증 또는 제공자 키

API 키는 저장소 시크릿으로 넣어둡니다.

gh secret set ANTHROPIC_API_KEY < /path/to/key.txt

모델까지 지정하려면 객체 형태로 풀어 씁니다.

engine:
  id: claude
  model: claude-haiku-4-5

이미 클로드 코드 GitHub Actions를 쓰고 있다면 둘의 차이가 궁금하실 텐데요. 클로드 코드 액션은 Claude 하나를 GitHub Actions에서 돌리는 데 집중하고, gh-aw는 엔진을 갈아 끼울 수 있는 대신 권한 분리와 컴파일 단계를 자기 방식으로 강제합니다. 저장소 자동화를 여러 개 굴리면서 보안 기준을 일관되게 맞추고 싶을 때 gh-aw 쪽이 편합니다.

비용은 AI Credit(이하 AIC)이라는 단위로 표시됩니다. 1 AIC가 0.01달러라서 100 AIC면 1달러입니다. 실행 한 번의 기본 상한은 1,000 AIC, 곧 10달러입니다. 이 값은 조절할 수 있고 하루 단위 상한도 따로 걸 수 있습니다.

max-ai-credits: 500
max-daily-ai-credits: 15000
max-turns: 20

max-turns는 대화 반복 횟수를 묶는 안전장치입니다. 에이전트가 같은 자리를 맴돌며 토큰을 태우는 상황을 막아주죠.

쓴 만큼은 CLI로 확인합니다. 실행 목록과 토큰 사용량은 gh aw logs로, 특정 실행의 도구 호출 내역까지 파고들려면 gh aw audit을 씁니다.

gh aw logs --start-date -30d
gh aw audit <run-id>
gh aw status

비용을 줄이는 가장 확실한 방법은 에이전트를 아예 시작하지 않는 겁니다. skip-if-match로 걸러내면 사전 활성화 단계에서 끝나니 추론 비용이 0이고요.

on:
  issues:
    types: [opened]
  skip-if-match: "label:duplicate OR label:wont-fix"

이벤트마다 반응하는 대신 하루에 한 번 모아서 처리하는 것도 방법입니다. 공통 지시문을 한 번만 보내고 여러 항목을 이어서 처리하니 캐시가 잘 먹거든요. push나 check_run 같은 트리거는 활발한 저장소에서 하루에 수백 번씩 돌 수 있으니, 처음에는 schedule이나 workflow_dispatch로 시작해서 감을 잡는 편이 안전합니다.

마치며

지금까지 gh-aw가 마크다운을 GitHub Actions 워크플로우로 바꾸는 과정을 따라가 봤습니다. 25줄짜리 파일이 1,830줄로 부풀면서 액션 SHA 고정, 네트워크 방화벽, 콘텐츠 정제, 권한 분리가 한꺼번에 붙는 걸 확인했고요.

가장 기억할 만한 설계는 역시 세이프 아웃풋입니다. AI가 무엇을 하고 싶은지 말하게 하되 실제로 하는 건 별도의 잡에 맡기는 구조인데, 에이전트를 신뢰할 수 없다는 전제를 그대로 워크플로우 모양에 새겨 넣은 셈이죠. AI를 저장소에 들이는 다른 도구를 평가할 때도 이 기준으로 보면 판단이 빨라집니다. 이 도구는 에이전트에게 무슨 권한을 직접 쥐여주고 있나, 하고 말이죠.

처음 도입한다면 갤러리의 이슈 분류 워크플로우를 staged: true로 붙여놓고 며칠 지켜보시길 권합니다. 에이전트가 어떤 판단을 내리는지 눈으로 확인한 다음 실제 쓰기를 열어도 늦지 않으니까요. 조직 단위로 넓힐 때는 gh aw env로 모델과 예산 기본값을 GitHub Actions 변수에 심어두면 저장소마다 같은 기준을 맞추기 수월합니다.

더 자세한 내용은 GitHub Agentic Workflows 공식 문서를 참고하세요.

This work is licensed under CC BY 4.0CCBY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord