<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>發布 on Tarragon</title><link>https://tarrragon.github.io/blog/tags/%E7%99%BC%E5%B8%83/</link><description>Recent content in 發布 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 06 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/%E7%99%BC%E5%B8%83/index.xml" rel="self" type="application/rss+xml"/><item><title>CI/CD 失敗到修復發布流程</title><link>https://tarrragon.github.io/blog/ci/github-actions-failure-flow/</link><pubDate>Wed, 06 May 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/ci/github-actions-failure-flow/</guid><description>&lt;p>CI/CD 失敗處理的核心責任是把紅燈轉成明確的下一步路由。紅燈本身是驗證或交付層的訊號；工程流程要做的是找出失敗層、重現同一個條件、修正後重新讓 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合如何在合併前自動驗證變更品質與相容性">CI Pipeline&lt;/a> 證明變更可發布。&lt;/p>
&lt;h2 id="失敗後先看什麼">失敗後先看什麼&lt;/h2>
&lt;p>失敗後第一步是定位 workflow 與 job。CI/CD 系統會把一次 push、pull request、tag 或 release 拆成多個 workflow，每個 workflow 下面又有多個 job；真正的下一步取決於是哪一層失敗。&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>失敗位置&lt;/th>
 &lt;th>常見原因&lt;/th>
 &lt;th>下一步路由&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>Lint / format&lt;/td>
 &lt;td>程式碼、文件或設定格式不符&lt;/td>
 &lt;td>回本機跑同一條 lint / format 命令&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Test&lt;/td>
 &lt;td>單元、整合、瀏覽器或裝置測試回歸&lt;/td>
 &lt;td>下載 report，回本機用同條件重現&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Build&lt;/td>
 &lt;td>編譯、bundle、package 或靜態產物失敗&lt;/td>
 &lt;td>回本機跑 production build 入口&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Package&lt;/td>
 &lt;td>image、app bundle、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact&lt;/a> 產生失敗&lt;/td>
 &lt;td>檢查版本、簽章、registry 或路徑&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Deploy&lt;/td>
 &lt;td>hosting、runtime、store 或權限設定&lt;/td>
 &lt;td>先確認 build &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact&lt;/a> 是否已成功&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>Lint / format 失敗代表靜態契約沒有通過。常見情境是程式格式、文件格式、型別檢查、schema 或設定規則不符合規範。這類失敗的修復路徑通常很短：讀錯誤訊息、修正來源、必要時跑 formatter，再提交修正。&lt;/p>
&lt;p>Test 失敗代表某個行為或契約沒有符合預期。這類失敗要先看 report、screenshot、trace、device log 或 error context，確認是功能真的回歸、測試假設過期，還是測試環境缺少 production-like artifact。直接改測試前，要先確認測試原本守的是哪個使用者或系統行為。&lt;/p>
&lt;p>Build 失敗代表 pipeline 尚未產生可部署產物。這類失敗通常來自編譯錯誤、bundle 設定、依賴版本、環境變數、template 或資源路徑。修復時以專案定義的 production build 命令作為最小重現入口。&lt;/p>
&lt;p>Deploy 失敗代表發布動作沒有完成。這類失敗需要先區分 artifact 是否存在、發布通道權限是否正確、環境保護是否放行。若測試與 build 已成功，deploy 失敗多半是發布通道問題；若 artifact 沒有產生，應回到 build 或 package 階段。&lt;/p>
&lt;h2 id="本機重現流程">本機重現流程&lt;/h2>
&lt;p>本機重現的責任是讓修復建立在同一個驗證條件上。CI 是用乾淨環境執行的一組命令；只要能在本機跑出同樣的失敗，修復就能被快速驗證。&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">make build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">make &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">make deploy-dry-run&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Build 命令驗證 production artifact 是否能產生。這一步應該接近 CI 使用的 build 入口，避免開發模式遮蔽 production 問題。&lt;/p>
&lt;p>Test 命令驗證產物或程式行為。前端可能是 browser test，後端可能是 integration / contract test，App 可能是 device test，Docker 可能是 image scan 或 smoke test。&lt;/p>
&lt;p>Deploy dry-run 命令驗證發布前條件。高風險部署至少要能檢查 artifact、權限、環境與版本資訊；沒有 dry-run 的專案，也應保留對等的 preflight check。&lt;/p>
&lt;h2 id="修復與重新觸發">修復與重新觸發&lt;/h2>
&lt;p>修復流程的核心是用新 commit 讓 CI 重新驗證。一般流程不需要刪掉失敗 commit，也不需要 force push；失敗 commit 留在歷史裡，後續 fix commit 會形成清楚的修復脈絡。&lt;/p>
&lt;ol>
&lt;li>讀失敗 job 的 log 或 artifact。&lt;/li>
&lt;li>在本機跑對應命令重現。&lt;/li>
&lt;li>修改最小必要範圍。&lt;/li>
&lt;li>跑同一條本機命令確認修復。&lt;/li>
&lt;li>commit 並 push。&lt;/li>
&lt;li>等 GitHub Actions 重新跑。&lt;/li>
&lt;/ol>
&lt;p>這個流程的好處是保留可追溯性。日後再看到同類失敗，可以從 commit history 與 CI log 找到當時的判讀方式。&lt;/p>
&lt;h2 id="發布-gate-路由">發布 gate 路由&lt;/h2>
&lt;p>發布 gate 的責任是把「是否進入下一階段」變成明確條件。這一頁只處理失敗後的操作路由；&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">required checks&lt;/a>、job &lt;code>needs&lt;/code>、&lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">environment protection&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">artifact handoff&lt;/a> 的設計原理，獨立放在 &lt;a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>CI/CD 失敗處理的核心責任是把紅燈轉成明確的下一步路由。紅燈本身是驗證或交付層的訊號；工程流程要做的是找出失敗層、重現同一個條件、修正後重新讓 <a href="/blog/ci/knowledge-cards/ci-pipeline/" data-link-title="CI Pipeline" data-link-desc="說明持續整合如何在合併前自動驗證變更品質與相容性">CI Pipeline</a> 證明變更可發布。</p>
<h2 id="失敗後先看什麼">失敗後先看什麼</h2>
<p>失敗後第一步是定位 workflow 與 job。CI/CD 系統會把一次 push、pull request、tag 或 release 拆成多個 workflow，每個 workflow 下面又有多個 job；真正的下一步取決於是哪一層失敗。</p>
<table>
  <thead>
      <tr>
          <th>失敗位置</th>
          <th>常見原因</th>
          <th>下一步路由</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Lint / format</td>
          <td>程式碼、文件或設定格式不符</td>
          <td>回本機跑同一條 lint / format 命令</td>
      </tr>
      <tr>
          <td>Test</td>
          <td>單元、整合、瀏覽器或裝置測試回歸</td>
          <td>下載 report，回本機用同條件重現</td>
      </tr>
      <tr>
          <td>Build</td>
          <td>編譯、bundle、package 或靜態產物失敗</td>
          <td>回本機跑 production build 入口</td>
      </tr>
      <tr>
          <td>Package</td>
          <td>image、app bundle、<a href="/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact</a> 產生失敗</td>
          <td>檢查版本、簽章、registry 或路徑</td>
      </tr>
      <tr>
          <td>Deploy</td>
          <td>hosting、runtime、store 或權限設定</td>
          <td>先確認 build <a href="/blog/ci/knowledge-cards/artifact/" data-link-title="Artifact" data-link-desc="說明 CI/CD 中可被驗證、保存與發布的交付產物">artifact</a> 是否已成功</td>
      </tr>
  </tbody>
</table>
<p>Lint / format 失敗代表靜態契約沒有通過。常見情境是程式格式、文件格式、型別檢查、schema 或設定規則不符合規範。這類失敗的修復路徑通常很短：讀錯誤訊息、修正來源、必要時跑 formatter，再提交修正。</p>
<p>Test 失敗代表某個行為或契約沒有符合預期。這類失敗要先看 report、screenshot、trace、device log 或 error context，確認是功能真的回歸、測試假設過期，還是測試環境缺少 production-like artifact。直接改測試前，要先確認測試原本守的是哪個使用者或系統行為。</p>
<p>Build 失敗代表 pipeline 尚未產生可部署產物。這類失敗通常來自編譯錯誤、bundle 設定、依賴版本、環境變數、template 或資源路徑。修復時以專案定義的 production build 命令作為最小重現入口。</p>
<p>Deploy 失敗代表發布動作沒有完成。這類失敗需要先區分 artifact 是否存在、發布通道權限是否正確、環境保護是否放行。若測試與 build 已成功，deploy 失敗多半是發布通道問題；若 artifact 沒有產生，應回到 build 或 package 階段。</p>
<h2 id="本機重現流程">本機重現流程</h2>
<p>本機重現的責任是讓修復建立在同一個驗證條件上。CI 是用乾淨環境執行的一組命令；只要能在本機跑出同樣的失敗，修復就能被快速驗證。</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">make build
</span></span><span class="line"><span class="ln">2</span><span class="cl">make <span class="nb">test</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">make deploy-dry-run</span></span></code></pre></div><p>Build 命令驗證 production artifact 是否能產生。這一步應該接近 CI 使用的 build 入口，避免開發模式遮蔽 production 問題。</p>
<p>Test 命令驗證產物或程式行為。前端可能是 browser test，後端可能是 integration / contract test，App 可能是 device test，Docker 可能是 image scan 或 smoke test。</p>
<p>Deploy dry-run 命令驗證發布前條件。高風險部署至少要能檢查 artifact、權限、環境與版本資訊；沒有 dry-run 的專案，也應保留對等的 preflight check。</p>
<h2 id="修復與重新觸發">修復與重新觸發</h2>
<p>修復流程的核心是用新 commit 讓 CI 重新驗證。一般流程不需要刪掉失敗 commit，也不需要 force push；失敗 commit 留在歷史裡，後續 fix commit 會形成清楚的修復脈絡。</p>
<ol>
<li>讀失敗 job 的 log 或 artifact。</li>
<li>在本機跑對應命令重現。</li>
<li>修改最小必要範圍。</li>
<li>跑同一條本機命令確認修復。</li>
<li>commit 並 push。</li>
<li>等 GitHub Actions 重新跑。</li>
</ol>
<p>這個流程的好處是保留可追溯性。日後再看到同類失敗，可以從 commit history 與 CI log 找到當時的判讀方式。</p>
<h2 id="發布-gate-路由">發布 gate 路由</h2>
<p>發布 gate 的責任是把「是否進入下一階段」變成明確條件。這一頁只處理失敗後的操作路由；<a href="/blog/ci/knowledge-cards/required-checks/" data-link-title="Required Checks" data-link-desc="說明 pull request 的必要檢查如何作為合併 gate">required checks</a>、job <code>needs</code>、<a href="/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">environment protection</a> 與 <a href="/blog/ci/knowledge-cards/artifact-handoff/" data-link-title="Artifact Handoff" data-link-desc="說明測試與部署如何共用同一份可追溯產物">artifact handoff</a> 的設計原理，獨立放在 <a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</p>
<h2 id="常見處理情境">常見處理情境</h2>
<p>CI 失敗但本機通過時，優先檢查環境差異。常見差異包括語言版本、套件管理器版本、缺少子模組、缺少 build artifact、測試依賴未安裝、時區或檔案大小寫差異。這類問題要把版本與建置前置條件寫進 workflow、Makefile 或 script，讓重現條件成為專案的一部分。</p>
<p>測試不穩定時，優先把 <a href="/blog/ci/knowledge-cards/flaky-test/" data-link-title="Flaky Test" data-link-desc="說明非決定性測試如何降低 CI gate 信任度與治理方式">Flaky Test</a> 狀態標出來並建立 owner。短期可以隔離或重跑，長期要找到不穩定來源，例如等待條件錯誤、外部網路依賴、時間假設、測試資料不穩或動畫 transition 尚未完成。測試不穩定會降低 gate 信任度，因此它本身就是需要治理的 CI 問題。</p>
<p>Deploy 失敗但測試通過時，優先看 artifact 與權限。若 build output 存在且可下載，問題通常在部署通道、token permission 或 <a href="/blog/ci/knowledge-cards/environment-protection/" data-link-title="Environment Protection" data-link-desc="說明目標環境的審核、權限與放行條件如何保護發布">environment protection</a>；若 artifact 缺失，就回到 build job。</p>
<h2 id="反模式與替代做法">反模式與替代做法</h2>
<table>
  <thead>
      <tr>
          <th>反模式</th>
          <th>風險</th>
          <th>替代做法</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>看到紅燈直接重跑</td>
          <td>掩蓋 flaky 或環境問題</td>
          <td>先看失敗 log，再決定是否重跑</td>
      </tr>
      <tr>
          <td>用 <code>--no-verify</code> 或跳過 CI</td>
          <td>把局部問題帶進主線</td>
          <td>修掉 gate 或明確記錄例外</td>
      </tr>
      <tr>
          <td>CI 與本機命令不同</td>
          <td>本機通過但 CI 失敗</td>
          <td>把命令收斂到 Makefile / npm script</td>
      </tr>
      <tr>
          <td>測試直接打外部服務</td>
          <td>網路與第三方狀態污染判斷</td>
          <td>使用 fixture、mock 或可控環境</td>
      </tr>
  </tbody>
</table>
<p>反模式的共同問題是讓 CI 失去判讀價值。CI 的目標是讓綠燈代表「這次變更在定義好的條件下可發布」。</p>
<h2 id="最小可用流程">最小可用流程</h2>
<p>最小可用流程是讓每次變更都有同一條路徑。對小型靜態網站或個人 blog，先做到以下四件事，就能形成穩定發布節奏。</p>
<ol>
<li><code>push</code> 或 PR 觸發 lint / test / build。</li>
<li>production build 有單一入口。</li>
<li>測試失敗時保留 artifact 或 report。</li>
<li>deploy 只接受測試與 build 通過後的產物。</li>
</ol>
<p>這套流程建立後，CI 紅燈就會成為清楚的路由訊號：哪一層壞、用哪個命令重現、修完後用哪個 gate 放行。</p>
<p>若變更涉及後端服務，可再對照 backend 知識卡的 <a href="/blog/backend/knowledge-cards/runbook/" data-link-title="Runbook" data-link-desc="說明 runbook 如何把事故判斷與操作步驟標準化">Runbook</a>、<a href="/blog/backend/knowledge-cards/rollback-strategy/" data-link-title="Rollback Strategy" data-link-desc="說明事故期間如何判斷回滾、回切與暫停變更">Rollback Strategy</a> 與 <a href="/blog/backend/knowledge-cards/release-gate/" data-link-title="Release Gate" data-link-desc="說明變更在正式釋出前如何通過或阻擋">Release Gate</a> 進一步細化故障處理順序與放行條件。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>需要理解 CI 在可靠性模組的位置：讀 <a href="/blog/backend/06-reliability/ci-pipeline/" data-link-title="6.1 CI pipeline" data-link-desc="CI pipeline 的分層策略、artifact 管理、flaky 治理與 release gate 輸入">6.1 CI pipeline</a>。</li>
<li>需要看靜態站部署案例：讀 <a href="../blog-project-deploy/">本 blog 專案部署</a>。</li>
<li>需要理解 CI gate 設計：讀 <a href="../ci-gate-workflow-boundary/">CI gate 與 workflow 邊界</a>。</li>
<li>需要理解發布阻擋策略：讀 <a href="/blog/backend/06-reliability/release-gate/" data-link-title="6.8 Release Gate 與變更節奏" data-link-desc="把驗證、migration、相容性納入放行判準">6.8 Release Gate 與變更節奏</a>。</li>
</ul>
]]></content:encoded></item></channel></rss>