# Github Actions

> GitHub Actions CI/CD 最佳實踐指南。當需要設定 workflow、cron 排程、GitHub Pages 部署、使用 Octokit API、或處理 rate limiting 時使用。

- Skill: `recca0120/github-actions` (Agent Skill)
- Install (CLI): `npx skillmds@latest add recca0120/github-actions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/recca0120/github-actions/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: recca0120 (https://skillmd.com/u/recca0120)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/recca0120/github-actions

---


# GitHub Actions Best Practices Guide

## Runner 環境

| Runner | OS | Node.js |
|--------|-----|---------|
| `ubuntu-latest` | Ubuntu 24.04 | Node 20（2026-06 起預設 Node 24） |
| `ubuntu-24.04` | Ubuntu 24.04（固定版本） | 同上 |

**Node 20 EOL：2026-04**。建議新專案直接用 Node 22。

## Cron 排程

```yaml
on:
  schedule:
    - cron: '17 3 * * 1'   # 每週一 03:17 UTC（避開整點）
  workflow_dispatch:          # 搭配手動觸發做測試
```

**注意事項**：
- 所有排程為 **UTC** 時區
- **避免整點** `:00` — GitHub 尖峰負載，延遲較大。用 `:17`、`:43` 等偏移
- 排程 workflow 只在 **default branch** 執行
- Repo 60 天無活動 → GitHub 自動停用排程 workflow

## GitHub Pages 部署

```yaml
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-pages-artifact@v4
        with:
          path: ./dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4
```

**必要**：`pages: write` + `id-token: write` 權限、`actions/upload-pages-artifact@v4`。

## Commit 回 Repo

```yaml
permissions:
  contents: write

steps:
  - uses: actions/checkout@v4
  - name: Generate data
    run: node scripts/collect-data.js
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  - name: Commit and push
    run: |
      git config user.name "github-actions[bot]"
      git config user.email "github-actions[bot]@users.noreply.github.com"
      git add data/
      git diff --staged --quiet || git commit -m "Update data [skip ci]"
      git push
```

**重要**：
- `[skip ci]` 防止無限觸發
- `git diff --staged --quiet || git commit` 避免空 commit 失敗
- GITHUB_TOKEN 的 commit **不會觸發**其他 workflow（防迴圈）
- 需要觸發其他 workflow → 用 PAT

## Octokit.js（GitHub REST API）

```typescript
import { Octokit } from '@octokit/rest';
import { throttling } from '@octokit/plugin-throttling';
import { retry } from '@octokit/plugin-retry';

const MyOctokit = Octokit.plugin(throttling, retry);
const octokit = new MyOctokit({
  auth: process.env.GITHUB_TOKEN,
  throttle: {
    onRateLimit: (retryAfter, options) => {
      console.warn(`Rate limit hit, retrying after ${retryAfter}s`);
      return true;
    },
    onSecondaryRateLimit: (retryAfter, options) => {
      console.warn(`Secondary rate limit hit`);
      return true;
    },
  },
});
```

### Search Users API

```typescript
const { data } = await octokit.rest.search.users({
  q: 'location:Taiwan followers:>10',
  sort: 'followers',
  order: 'desc',
  per_page: 100,
  page: 1,
});
// data.total_count, data.items[]
```

**Rate limits**：
- Search API：30 req/min（authenticated）、10 req/min（unauthenticated）
- Core API：5,000 req/hr（authenticated）
- 用 `@octokit/plugin-throttling` 自動處理

## Workflow 串接

### 方式一：workflow_run（同 repo）

```yaml
# 被觸發的 workflow
on:
  workflow_run:
    workflows: ["Data Collection"]
    types: [completed]
```

### 方式二：repository_dispatch（跨 repo，需 PAT）

```yaml
- name: Trigger deploy
  run: |
    curl -X POST \
      -H "Authorization: token ${{ secrets.PAT_TOKEN }}" \
      https://api.github.com/repos/owner/repo/dispatches \
      -d '{"event_type":"deploy"}'
```

**注意**：GITHUB_TOKEN **無法**觸發 `workflow_dispatch` 或 `repository_dispatch`。

### 方式三：Reusable workflows

```yaml
jobs:
  deploy:
    uses: ./.github/workflows/reusable-deploy.yml
    secrets: inherit
```

## Caching

```yaml
- uses: actions/setup-node@v4
  with:
    node-version: 22
    cache: 'npm'   # 內建 cache 支援
- run: npm ci
```

快取 `~/.npm`（不要快取 `node_modules`，因為 `npm ci` 會先刪除它）。

快取限制：10 GB/repo，7 天無存取自動過期。

## 安全最佳實踐

1. **Pin action 到完整 SHA**：
   ```yaml
   uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
   ```
2. **最小權限**：明確宣告 `permissions`
3. **不要在 shell 中內插不受信任的資料**：
   ```yaml
   # 錯誤：run: echo "${{ github.event.issue.title }}"
   # 正確：用 env
   env:
     TITLE: ${{ github.event.issue.title }}
   run: echo "$TITLE"
   ```
4. 避免 `pull_request_target`（fork PR 有 secrets 存取權）
5. 用 OIDC 取代靜態 secrets（雲端 credentials）

## 常見陷阱

1. 排程 workflow 在無活動 60 天後自動停用
2. GITHUB_TOKEN commit 不觸發其他 workflow
3. Cron 可能延遲 5-15+ 分鐘，不要依賴精確時間
4. YAML 格式錯誤佔 19% pipeline 錯誤 → 用 linter
5. 未加 `[skip ci]` 的自動 commit → 無限迴圈
6. 未 pin actions → 供應鏈攻擊風險

