<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Bobo 的學思山丘</title><description>學習、思考、紀錄。愛分享、愛開發的軟體工程師，二寶爸的技術與生活觀察。</description><link>https://bobochen.dev/</link><item><title>把 DMARC 推到強制的那天:三個「看起來成功」的假訊號</title><link>https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/</link><guid isPermaLink="true">https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/</guid><description>SPF、DKIM、DMARC 都設好了,dig 也查得到——但其中兩條寄件管道其實根本沒在簽章。記錄推到 p=reject 那天踩到的三個假訊號,以及一張讓人最安心也最危險的資料表。</description><pubDate>Mon, 07 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;三週前我寫了 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 那份 playbook&lt;/a&gt;,把 DMARC 從 &lt;code&gt;p=none&lt;/code&gt; 推到 &lt;code&gt;p=reject&lt;/code&gt; 的分階段流程整理得很整齊。這篇是實際執行那天的記錄。&lt;/p&gt;
&lt;p&gt;實際的節奏跟原訂計畫不完全一樣:監控期比 playbook 寫的短,&lt;code&gt;quarantine&lt;/code&gt; 那一階也沒有停滿。這是公司內部的排程決定,就照著做了。結果是沒出事,但過程中踩到三個「看起來已經完成、實際上沒有」的訊號——那才是這篇要記的東西。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 這是「誰在冒用你的 Domain 寄信」系列的第 4 篇,執行紀錄。完全不熟 SPF/DKIM/DMARC 先看 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 白話入門&lt;/a&gt;;想照流程自己做一次,看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 實戰 playbook&lt;/a&gt;;整件事的起點是 &lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;#1 我如何重現一個資安評等的 F&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;先講結果&quot;&gt;先講結果&lt;/h2&gt;
&lt;p&gt;當天做完的東西:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;12 筆 DNS 變更&lt;/strong&gt;(SPF、DKIM、DMARC 三類)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6 條寄件管道&lt;/strong&gt;逐條盤點確認&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;11 條 DKIM 鏈&lt;/strong&gt;全部追到最終記錄驗過&lt;/li&gt;
&lt;li&gt;最後把 DMARC 切到 &lt;code&gt;p=reject&lt;/code&gt;,SPF 收緊成 &lt;code&gt;-all&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;驗收方式很土:寄一封測試信到 Gmail,開「顯示原始郵件」,看那三行。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;三行全綠。看起來就是「做完了」。&lt;/p&gt;
&lt;p&gt;問題是,這張截圖只證明了&lt;strong&gt;我手動寄的這一封&lt;/strong&gt;通過。當天真正花時間的,是搞清楚另外五條管道到底有沒有跟上——而那件事,&lt;code&gt;dig&lt;/code&gt; 查不出來。&lt;/p&gt;
&lt;h2 id=&quot;三個假訊號住在哪裡&quot;&gt;三個假訊號住在哪裡&lt;/h2&gt;
&lt;p&gt;一封信從「我設定好了」到「對方真的收下」,中間有五個環節。我原本以為只要第一個對了,後面就會跟著對:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;三個假訊號各自住在其中一個縫隙裡。(編號是我遇到的順序,不是管線上的順序。)&lt;/p&gt;
&lt;h2 id=&quot;假訊號-1dns-加了-dkim--dkim-開了&quot;&gt;假訊號 #1:DNS 加了 DKIM ≠ DKIM 開了&lt;/h2&gt;
&lt;p&gt;Zendesk 的設定文件會叫你在 DNS 加兩筆 CNAME:&lt;code&gt;zendesk1&lt;/code&gt; 和 &lt;code&gt;zendesk2&lt;/code&gt;。我加了,&lt;code&gt;dig&lt;/code&gt; 查得到,一切看起來完成。&lt;/p&gt;
&lt;p&gt;但 Zendesk 後台還有一個勾選框叫 &lt;strong&gt;&lt;code&gt;Custom domain for DKIM&lt;/code&gt;&lt;/strong&gt;。沒勾之前,Zendesk 照樣用它自己的 &lt;code&gt;d=zendesk.com&lt;/code&gt; 簽章。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;用 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 那條姓氏規則&lt;/a&gt;來說:火漆印確實有蓋,但印上壓的是 &lt;strong&gt;Zendesk 家的姓&lt;/strong&gt;,不是我們家的。對齊要的是同姓,不同姓就不算數——連分部都不是。&lt;/p&gt;
&lt;p&gt;而最陰險的地方是:&lt;strong&gt;從 DNS 那端完全看不出差別。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;左右兩條路,&lt;code&gt;dig&lt;/code&gt; 看到的東西&lt;strong&gt;一模一樣&lt;/strong&gt;。差別只存在於一個別人家後台裡的勾選框。&lt;/p&gt;
&lt;p&gt;Google Workspace 是同一個模式:DKIM 的 TXT 加進 DNS 之後,還要回後台按 &lt;strong&gt;Start authentication&lt;/strong&gt;,沒按就是沒開。&lt;/p&gt;
&lt;h3 id=&quot;順序不能反&quot;&gt;順序不能反&lt;/h3&gt;
&lt;p&gt;Zendesk 自己的文件把這件事寫得很清楚:那個開關要等 &lt;strong&gt;CNAME 生效之後&lt;/strong&gt;才能開。反過來做——先勾開關、CNAME 還沒生效——送出去的信會帶著一把查不到公鑰的簽章,DKIM 這條路直接廢掉;在 &lt;code&gt;p=reject&lt;/code&gt; 之下,能不能撐住就只剩 SPF 那條沒把握的路。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;也就是說,這個開關&lt;strong&gt;早開會讓 DKIM 直接失效、晚開會沒對齊&lt;/strong&gt;——&lt;code&gt;p=reject&lt;/code&gt; 之下兩邊都是把賭注壓在沒把握的 SPF 上。順序本身就是唯一的安全路徑。&lt;/p&gt;
&lt;h3 id=&quot;一個要講清楚的精確度&quot;&gt;一個要講清楚的精確度&lt;/h3&gt;
&lt;p&gt;DKIM 沒對齊&lt;strong&gt;不等於&lt;/strong&gt; DMARC 一定失敗。DMARC 是 &lt;strong&gt;OR 邏輯&lt;/strong&gt;——SPF 對齊且通過、或 DKIM 對齊且通過,任一成立就算 pass。所以 &lt;code&gt;d=zendesk.com&lt;/code&gt; 的信會不會被退,還要看那條管道的 SPF 對齊狀況。&lt;/p&gt;
&lt;p&gt;網路上關於 Zendesk 的 SPF 對齊有互相矛盾的說法,它自己的文件也沒交代 Return-Path 怎麼設,我沒辦法證實任何一邊。所以當天的做法很單純:&lt;strong&gt;把 DKIM 這條補到確定對齊,不去賭 SPF 那條。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;怎麼真的驗&lt;/strong&gt;:寄一封測試信,看 &lt;code&gt;Authentication-Results&lt;/code&gt; 裡的 &lt;code&gt;dkim=pass header.d=&lt;/code&gt; 是誰。&lt;code&gt;header.d=&lt;/code&gt; 是你的網域才算數。這是唯一從外面看得出差別的方法。&lt;/p&gt;
&lt;h2 id=&quot;假訊號-2cname-解得到--dkim-有效&quot;&gt;假訊號 #2:CNAME 解得到 ≠ DKIM 有效&lt;/h2&gt;
&lt;p&gt;Shopify 這條的狀況不一樣,而且更難發現。&lt;/p&gt;
&lt;p&gt;它的 selector CNAME 指向 &lt;strong&gt;100% 正確&lt;/strong&gt;——名字對、目標對、&lt;code&gt;dig&lt;/code&gt; 一路都解得到。但把這條鏈追到最終目標的 TXT,那格是&lt;strong&gt;空的&lt;/strong&gt;。目標端當下還沒 provision 好。&lt;/p&gt;
&lt;p&gt;比喻的話:DNS 上的路標完全正確,指向存放印模的櫃子;但櫃子當下是空的。郵局照路標去找,找到一個空櫃,驗不了章。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;大部分人(包括我)查到第二格就收工了,因為看到 CNAME 有回應就以為結束了。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 只查 CNAME:看得到指向,看不到內容
dig +short CNAME selector._domainkey.yourdomain.com

# 追到底:這才是收件方真正拿去驗章的東西
dig +short TXT selector._domainkey.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二個指令的回傳裡如果看不到 &lt;code&gt;v=DKIM1; p=...&lt;/code&gt; 那一段,就是還沒好。&lt;strong&gt;驗 DKIM 不能只看 CNAME 有沒有解到,要追到最終的 TXT。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;後來 Shopify 那端 provision 完成,後台狀態才轉成 &lt;code&gt;Authenticated&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id=&quot;同一個-preject兩家-saas-的反應完全不同&quot;&gt;同一個 p=reject,兩家 SaaS 的反應完全不同&lt;/h3&gt;
&lt;p&gt;這是當天另一個意外收穫。同樣是「你的網域已經 &lt;code&gt;p=reject&lt;/code&gt;、但這條管道沒設好」:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zendesk&lt;/strong&gt;:照寄。萬一那條管道的 SPF 也沒對齊,信就會被收件方退掉——至少會有退信、有聲音。(前面說過,Zendesk 的 SPF 對齊我沒能證實,所以這條路徑實際會不會退,我只能說「有機會有聲音」。)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shopify&lt;/strong&gt;:偵測到驗證記錄沒設好時,把 From &lt;strong&gt;換成它自己的網域&lt;/strong&gt;寄出去。不會退信,但寄件人不是你。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Shopify 文件寫的觸發條件是「authentication records aren’t configured」,&lt;strong&gt;不是&lt;/strong&gt;「偵測到你的網域有 DMARC」。這兩件事在 debug 的時候很容易被講混,講混了就會去查錯的東西。&lt;/p&gt;
&lt;p&gt;「不會退信」聽起來比較友善,但它其實更難抓:信有送到、收件人也讀了,只是寄件人顯示的不是你的品牌。整條路上沒有任何錯誤訊息——除非你自己去看那封收到的信長什麼樣子。&lt;/p&gt;
&lt;h2 id=&quot;假訊號-3email_logs-有紀錄--信送到了&quot;&gt;假訊號 #3:&lt;code&gt;email_logs&lt;/code&gt; 有紀錄 ≠ 信送到了&lt;/h2&gt;
&lt;p&gt;這是當天最有料的一個。&lt;/p&gt;
&lt;p&gt;我們自己有一張 &lt;code&gt;email_logs&lt;/code&gt;,每寄一封信就寫一列。要驗「今天的系統信有沒有正常寄出」,直覺就是去查這張表:表裡有那一列 → 寄出去了。&lt;/p&gt;
&lt;p&gt;不是。&lt;/p&gt;
&lt;p&gt;Laravel 的 &lt;code&gt;MessageSent&lt;/code&gt; 事件,是在 &lt;code&gt;Mailer.php&lt;/code&gt; 把訊息交給 transport &lt;strong&gt;之後&lt;/strong&gt;才觸發的。用 Mailgun 的話,那個「之後」的精確時間點是——&lt;strong&gt;Mailgun API 回 HTTP 200 的那一刻&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而 Mailgun 那個 200 的 response body,依它自己的文件是:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{&quot;id&quot;: &quot;&amp;#x3C;20260907...@mg.yourdomain.com&gt;&quot;, &quot;message&quot;: &quot;Queued. Thank you.&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(這是 Mailgun 文件記載的回應字串,不是我當天抓的封包。)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Queued.&lt;/strong&gt; 它一直很誠實。是我們把「排進佇列」讀成了「寄到了」。&lt;/p&gt;
&lt;p&gt;而我們那張表存的是:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 我們自己的 listener(示意)
class LogSentMessage implements ShouldQueue
{
    public function handle(MessageSent $event): void
    {
        EmailLog::create([
            &apos;from&apos;    =&gt; ...,
            &apos;to&apos;      =&gt; ...,
            &apos;subject&apos; =&gt; ...,
            &apos;body&apos;    =&gt; ...,
        ]);   // 沒有 status、沒有存 Mailgun 回的 message id
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以一封被 &lt;code&gt;550&lt;/code&gt; 退掉的信,在這張表裡跟一封成功送達的信&lt;strong&gt;長得一模一樣&lt;/strong&gt;:&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;送達的和被退回的,在 &lt;code&gt;email_logs&lt;/code&gt; 裡是同一種樣子。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;畫成時序圖更清楚——問題不在哪一步做錯了,而在&lt;strong&gt;最後那條回饋路徑根本不存在&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;兩個容易被忽略的精確度細節&quot;&gt;兩個容易被忽略的精確度細節&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;(a) transport 判斷成功是精確比對 HTTP 200,不是任何 2xx。&lt;/strong&gt; Mailgun 目前就是回 200,所以沒事;但這個假設沒有寫在任何地方,哪天對方改成 &lt;code&gt;202 Accepted&lt;/code&gt; 之類的,行為會怎麼變是另一件事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(b) 寫這張表的 listener 是 &lt;code&gt;ShouldQueue&lt;/code&gt;。&lt;/strong&gt; 那一列是 &lt;strong&gt;queue worker&lt;/strong&gt; 寫進去的,不是寄信的那個 request 寫的。這代表這張表&lt;strong&gt;兩個方向都不能當證據&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有 row&lt;/strong&gt;,只證明 Mailgun 回了 200 Queued。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺 row&lt;/strong&gt;,什麼都證明不了——queue job 掉了、worker 沒跑、job 正在失敗重試,都會沒有 row。信可能好好地寄到了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;人寄的看得到系統寄的看不到&quot;&gt;人寄的看得到,系統寄的看不到&lt;/h2&gt;
&lt;p&gt;當天最後理清楚的是這件事:同樣被收件方擋掉,人跟系統的「可見度」差非常多。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;p=reject&lt;/code&gt; 上線後,如果哪條系統寄件管道沒設好,你會知道的唯一方式是——&lt;strong&gt;有人跑來跟你說「我沒收到」&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一個誠實的但書:也有收件方是先收下、再默默丟進垃圾桶或直接丟棄的,那種情況連退信都不會產生。所以反過來也成立——&lt;strong&gt;「沒有退信」也不等於「有送到」。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果你手上真的抓到退信,順帶提醒一句:Gmail 的 &lt;code&gt;550 5.7.26&lt;/code&gt; &lt;strong&gt;不是 DMARC 專用碼&lt;/strong&gt;,它有三種文件記載的意義。判讀請連錯誤訊息後面那串文字一起讀,只看數字會判錯方向。&lt;/p&gt;
&lt;h2 id=&quot;當天沒做的一件事和差點引錯的一份文件&quot;&gt;當天沒做的一件事,和差點引錯的一份文件&lt;/h2&gt;
&lt;h3 id=&quot;沒把對齊改成嚴格&quot;&gt;沒把對齊改成嚴格&lt;/h3&gt;
&lt;p&gt;當天有人問:既然都要推 &lt;code&gt;p=reject&lt;/code&gt; 了,要不要順便把 &lt;code&gt;adkim&lt;/code&gt; / &lt;code&gt;aspf&lt;/code&gt; 改成 &lt;code&gt;s&lt;/code&gt;(嚴格)?看起來比較安全。&lt;/p&gt;
&lt;p&gt;沒改,而且這是當天最果斷的一個決定。我們的交易信走 Mailgun,DKIM 的 &lt;code&gt;d=&lt;/code&gt; 和 Return-Path &lt;strong&gt;兩邊都掛在 &lt;code&gt;mg.&lt;/code&gt; 子網域&lt;/strong&gt;上。兩個 tag 同時收緊,這條管道的 SPF 和 DKIM 會在同一瞬間一起失去對齊——DMARC 的 OR 邏輯救不了它,因為兩條路同時斷了。整批交易信會當場掛掉。&lt;/p&gt;
&lt;p&gt;換來的收益幾乎是零。&lt;strong&gt;改成嚴格擋不掉一般的冒名信&lt;/strong&gt;——偽造信本來就不姓我們家的姓,在寬鬆規則下早就在失敗了。你多擋掉的幾乎都是自己的電子報、帳單和密碼重設信。姓氏規則的完整說明在 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&quot;rfc-7489-已經不是現行標準&quot;&gt;RFC 7489 已經不是現行標準&lt;/h3&gt;
&lt;p&gt;當天翻規範才發現的:DMARC 在 2026-05 由 &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9989&quot;&gt;RFC 9989&lt;/a&gt; 取代了 RFC 7489(同時也取代 RFC 9091),並從 Informational 升級成 &lt;strong&gt;Standards Track&lt;/strong&gt;。2026 年的文章還把 7489 當現行標準引用是錯的——包括我自己這系列前幾篇的初稿(已改)。&lt;/p&gt;
&lt;p&gt;連帶的實務影響是 &lt;code&gt;pct&lt;/code&gt; 標籤被移除了,「用 &lt;code&gt;pct=25&lt;/code&gt; → &lt;code&gt;50&lt;/code&gt; → &lt;code&gt;100&lt;/code&gt; 漸進放大」那套要重新想。這件事 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 已經整理過&lt;/a&gt;,這裡不重複。&lt;/p&gt;
&lt;h3 id=&quot;順帶gmail-2024-的要求不適用你以為的那些人&quot;&gt;順帶:Gmail 2024 的要求不適用你以為的那些人&lt;/h3&gt;
&lt;p&gt;Gmail 2024 的寄件者要求&lt;strong&gt;只適用於個人 &lt;code&gt;@gmail.com&lt;/code&gt; 收件者&lt;/strong&gt;,不適用 Google Workspace 託管的信箱。拿「Gmail 已經強制要求了」當理由去推內部排程之前,先確認你的收件方到底是哪一種——講錯了,下次就沒人相信你講的期限。&lt;/p&gt;
&lt;h2 id=&quot;三個假訊號對照表&quot;&gt;三個假訊號對照表&lt;/h2&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;假訊號&lt;/th&gt;&lt;th&gt;看起來像什麼&lt;/th&gt;&lt;th&gt;實際是什麼&lt;/th&gt;&lt;th&gt;怎麼真的驗&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;#1 SaaS 開關&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;dig&lt;/code&gt; 查得到 &lt;code&gt;zendesk1&lt;/code&gt; / &lt;code&gt;zendesk2&lt;/code&gt; 兩筆 CNAME&lt;/td&gt;&lt;td&gt;後台開關沒勾,還在用 &lt;code&gt;d=zendesk.com&lt;/code&gt; 簽&lt;/td&gt;&lt;td&gt;寄測試信,看 &lt;code&gt;Authentication-Results&lt;/code&gt; 的 &lt;code&gt;header.d=&lt;/code&gt; 是不是你的網域&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;#2 CNAME 鏈&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;CNAME 指向 100% 正確、一路解得到&lt;/td&gt;&lt;td&gt;目標端的 TXT 是空的,還沒 provision&lt;/td&gt;&lt;td&gt;&lt;code&gt;dig +short TXT&lt;/code&gt; 追到最終記錄,確認看得到 &lt;code&gt;v=DKIM1; p=&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;#3 應用層紀錄&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;email_logs&lt;/code&gt; 裡有那一列&lt;/td&gt;&lt;td&gt;那一列只代表 Mailgun 回了 &lt;code&gt;200 Queued&lt;/code&gt;&lt;/td&gt;&lt;td&gt;去 Mailgun 的 delivery log 對;長期解法是掛 webhook 收 bounce&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;收尾&quot;&gt;收尾&lt;/h2&gt;
&lt;p&gt;三個假訊號有個共同點很直白:&lt;strong&gt;我查得到的每一個訊號,都停在「我把東西交出去了」那一刻,沒有一個訊號來自「對方收下了」那一端。&lt;/strong&gt; &lt;code&gt;dig&lt;/code&gt; 停在 DNS、後台截圖停在設定頁、&lt;code&gt;email_logs&lt;/code&gt; 停在 API 回 200。&lt;/p&gt;
&lt;p&gt;那 12 筆 DNS 變更前後花不到一小時。驗這三件事花掉一整個下午。&lt;/p&gt;
&lt;p&gt;當天最後補上的東西不是 DNS 記錄,是把 Mailgun 的 delivery log 打開來當對照。至於把 bounce webhook 接進 &lt;code&gt;email_logs&lt;/code&gt;、讓那張表至少長出一個 &lt;code&gt;status&lt;/code&gt; 欄位——那件事還沒做。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;想從頭理解這三個 DNS 記錄在幹嘛,看 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 白話入門&lt;/a&gt;;想照分階段流程自己推一次,看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 實戰 playbook&lt;/a&gt;;想知道整件事為什麼會從一個 F 開始,回 &lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;系列起點&lt;/a&gt;。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/images/og/dmarc-enforcement-day-false-signals.png" medium="image"/><category>資安</category><category>email-security</category><category>DMARC</category><category>DKIM</category><category>Laravel</category><enclosure url="https://bobochen.dev/images/og/dmarc-enforcement-day-false-signals.png" length="0" type="image/png"/></item><item><title>HIS、PACS、AE Title 到底是什麼？醫院系統整合的名詞入門</title><link>https://bobochen.dev/blog/hospital-integration-glossary-ae-title-pacs-his/</link><guid isPermaLink="true">https://bobochen.dev/blog/hospital-integration-glossary-ae-title-pacs-his/</guid><description>醫院整合的信裡最常出現的三個縮寫：HIS 管文字、PACS 管圖、AE Title 是機器的名字。這篇用寄包裹的比喻，把它們加上 MWL、C-ECHO 一次講清楚。</description><pubDate>Tue, 01 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;名詞小教室hispacsae-title-一句話版&quot;&gt;名詞小教室：HIS、PACS、AE Title 一句話版&lt;/h2&gt;
&lt;p&gt;做醫院的系統整合，往來信件裡出現頻率最高的就是這三個縮寫：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HIS&lt;/strong&gt; —— 醫院的大腦，管文字（誰、什麼時候、做什麼、結果如何）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PACS&lt;/strong&gt; —— 影像倉庫，管圖（檢查做出來的東西存這裡）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AE Title&lt;/strong&gt; —— 機器在 DICOM 世界裡的名字&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;his-管文字pacs-管圖--醫院系統的第一條分界線&quot;&gt;HIS 管文字、PACS 管圖 —— 醫院系統的第一條分界線&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;HIS（Hospital Information System，醫院資訊系統）&lt;/strong&gt; 是醫院的大腦。誰掛號、住哪一床、今天要做什麼檢查、報告寫了什麼——全部在這裡。它管的是&lt;strong&gt;文字與流程&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PACS（Picture Archiving and Communication System，影像儲存與傳輸系統）&lt;/strong&gt; 是影像倉庫。X 光、超音波、CT、心電圖，所有「檢查做出來的東西」存在這裡，醫師要看的時候再調出來。它管的是&lt;strong&gt;圖&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;規模小一點、只服務單一科別或單一院區的，習慣叫 &lt;strong&gt;miniPACS&lt;/strong&gt;。功能上是同一件事，只是倉庫小一點。&lt;/p&gt;
&lt;p&gt;搞清楚這條線，你才知道自己的系統要接哪一邊。以心電圖設備來說，答案通常是：&lt;strong&gt;從 HIS 那邊拿到「要幫誰做檢查」，把做完的圖送進 PACS&lt;/strong&gt;。兩邊都要接，但接法完全不同。&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;HIS 管文字、PACS 管圖，設備夾在中間：左邊拿單、右邊送圖。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;ae-title-不是設定值是機器在-dicom-世界的名字&quot;&gt;AE Title 不是設定值，是機器在 DICOM 世界的名字&lt;/h2&gt;
&lt;p&gt;醫療影像設備之間講的共同語言叫 &lt;strong&gt;DICOM&lt;/strong&gt;。很多工程師第一次接觸會以為它是一種檔案格式，像 JPEG 那樣——這是最常見的誤會。DICOM 同時是一套&lt;strong&gt;網路通訊協定&lt;/strong&gt;，規定兩台機器怎麼建立連線、怎麼問問題、怎麼傳檔案。&lt;/p&gt;
&lt;p&gt;既然是機器對機器講話，就要有個叫法。這個叫法就是 &lt;strong&gt;AE Title&lt;/strong&gt;（Application Entity Title，應用實體名稱）。&lt;/p&gt;
&lt;p&gt;它的規則很樸素：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最多 &lt;strong&gt;16 個字元&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;大小寫有差&lt;/strong&gt;（&lt;code&gt;ECG-SERVER&lt;/code&gt; 和 &lt;code&gt;ecg-server&lt;/code&gt; 是兩個不同的東西）&lt;/li&gt;
&lt;li&gt;慣例上用大寫英數字加連字號，看起來像 &lt;code&gt;ECG-SERVER&lt;/code&gt;、&lt;code&gt;HOSP-IMGSRV&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重點是它的性質：&lt;strong&gt;AE Title 是身分，不是設定&lt;/strong&gt;。PACS 那端有一份名單，上面記著「我允許哪些名字、從哪些 IP 連進來」。名字不在名單上，連線在握手階段就被拒絕，你連錯誤訊息都不一定看得到。&lt;/p&gt;
&lt;p&gt;所以廠商問你的 AE Title，本質上是在問：&lt;strong&gt;我要把你加進白名單，你叫什麼名字？&lt;/strong&gt;&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;AE Title 是身分不是設定：對方的白名單認的是「名字 + 來源 IP」這一組。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id=&quot;三個欄位合起來才是一個地址住哪哪扇門叫什麼&quot;&gt;三個欄位合起來才是一個地址：住哪、哪扇門、叫什麼&lt;/h2&gt;
&lt;p&gt;新手最容易混的是 AE Title、IP、Port 這三個到底差在哪。用寄包裹想最快：&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;地址對、門對，但名字寫錯——包裹一樣退件。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;三個都對，門才會開。少一個、錯一個，連線都不會成立。&lt;/p&gt;
&lt;p&gt;順帶一提，&lt;code&gt;104&lt;/code&gt; 是 DICOM 的公定 port，&lt;code&gt;11112&lt;/code&gt; 是另一個常見的註冊 port。看到這兩個數字，你大概就知道那台是什麼東西。&lt;/p&gt;
&lt;h2 id=&quot;mwl工單不是我們產生的是醫院發下來的&quot;&gt;MWL：工單不是我們產生的，是醫院「發下來」的&lt;/h2&gt;
&lt;p&gt;還有一個名詞會在同一封信裡出現：&lt;strong&gt;MWL（Modality Worklist，檢查工作清單）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它就是&lt;strong&gt;今天的待辦清單&lt;/strong&gt;。醫師在 HIS 開了一張檢查單，這張單會流到 Worklist 服務上；設備（我們的平板、超音波機、X 光機）去問一句「今天有我的單嗎？」，Worklist 就回一份名單：誰、幾號單、做什麼檢查。&lt;/p&gt;
&lt;p&gt;這解決了醫院最怕的事——&lt;strong&gt;手打病歷號會錯&lt;/strong&gt;。護理師不用輸入病人資料，直接從清單上點，資料就跟著單子走。&lt;/p&gt;
&lt;p&gt;搭配這幾個動作看，整套 DICOM 就清楚了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C-ECHO&lt;/strong&gt; —— 敲門喊一聲「喂，聽得到嗎？」（只確認連線，不傳資料）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-FIND&lt;/strong&gt; —— 查詢，例如去 Worklist 問今天的單&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C-STORE&lt;/strong&gt; —— 送檔案，把做完的檢查推進 PACS&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;一張心電圖的旅程共五個步驟&quot;&gt;一張心電圖的旅程：共五個步驟&lt;/h2&gt;
&lt;p&gt;把上面全部串起來，一張心電圖從病人身上到醫師螢幕，是這樣走的（IP 與名稱都改成示意值）：&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;五步走完就是整張拓撲：文字從 HIS 出發、影像進 PACS、報告再回 HIS。&lt;/figcaption&gt;
&lt;/figure&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;#&lt;/th&gt;&lt;th&gt;發生什麼&lt;/th&gt;&lt;th&gt;誰跟誰講話&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;醫師在 HIS 開檢查單&lt;/td&gt;&lt;td&gt;HIS 內部&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;設備問「今天有誰要做？」&lt;/td&gt;&lt;td&gt;我們 → Worklist &lt;code&gt;HOSP-WKSRV / 10.0.0.20 : 3320&lt;/code&gt;（C-FIND）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;護理師錄心電圖，資料回主機&lt;/td&gt;&lt;td&gt;平板 → 我們的主機&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;主機把 DICOM 推進倉庫&lt;/td&gt;&lt;td&gt;&lt;code&gt;ECG-SERVER / 10.0.0.10 : 8090&lt;/code&gt; → &lt;code&gt;HOSP-IMGSRV / 10.0.0.20 : 104&lt;/code&gt;（C-STORE）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;醫師判讀完，文字報告回病歷&lt;/td&gt;&lt;td&gt;→ HIS&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;看懂這張表，開頭那封信該怎麼回就很明確了：廠商要的是&lt;strong&gt;第 4 步裡「送出去那一端」的身分&lt;/strong&gt;，也就是我們主機的 AE Title、IP、Port 三件組。&lt;/p&gt;
&lt;p&gt;實務上還要多講一句：心電圖送進 PACS 的格式是 &lt;strong&gt;DICOM Waveform&lt;/strong&gt;（波形），不是一般的影像 DICOM。很多 miniPACS 只收特定的 SOP Class，格式不對會被拒收——這件事一定要在串接前講清楚，不然雙方都會在「我明明送了、我明明沒收到」之間耗掉一個下午。想深入這一段的話，我在&lt;a href=&quot;https://bobochen.dev/blog/healthcare-software-development-14&quot;&gt;《DICOM 深入解析——不只是影像格式》&lt;/a&gt;寫過更細的踩坑；而心電圖 DICOM 存出來要用什麼打得開，則在&lt;a href=&quot;https://bobochen.dev/blog/ecg-dicom-viewer-weasis&quot;&gt;《心電圖存成 DICOM 之後，要用什麼打開？》&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id=&quot;想知道對方到底通不通echoscu-一行指令&quot;&gt;想知道對方到底通不通？echoscu 一行指令&lt;/h2&gt;
&lt;p&gt;講完名詞，給一個能立刻動手的東西。要驗證「白名單設好了沒」，不需要真的送一張心電圖過去——用 C-ECHO 敲門就好。&lt;/p&gt;
&lt;p&gt;最輕的工具是 &lt;a href=&quot;https://github.com/pydicom/pynetdicom&quot;&gt;pynetdicom&lt;/a&gt;（Python 實作的 DICOM 網路協定，pydicom 團隊維護）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3 分鐘快速上手&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. 安裝
pip install pynetdicom

# 2. 敲門：對 10.0.0.20 的 104 埠打招呼
python -m pynetdicom echoscu 10.0.0.20 104 -aet ECG-SERVER -aec HOSP-IMGSRV -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;兩個參數是整件事的重點，也剛好對應這篇的主題：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-aet&lt;/code&gt; = &lt;strong&gt;calling AE title&lt;/strong&gt;，我叫什麼（我方）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-aec&lt;/code&gt; = &lt;strong&gt;called AE title&lt;/strong&gt;，我要找誰（對方）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼判讀結果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;沒有錯誤、回 &lt;code&gt;Association Accepted&lt;/code&gt; → 通了，白名單有你&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Association Rejected&lt;/code&gt; → 網路是通的，但&lt;strong&gt;名字或來源 IP 沒被接受&lt;/strong&gt;，回頭找對方核對白名單&lt;/li&gt;
&lt;li&gt;連線 timeout → 還沒到 DICOM 這層，是防火牆或路由的問題&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;能分清楚後面兩種，你就不會把一個防火牆問題誤報成「對方 PACS 壞了」——這個誤判在現場非常常見。&lt;/p&gt;
&lt;h3 id=&quot;有趣發現&quot;&gt;有趣發現&lt;/h3&gt;
&lt;p&gt;上面那幾張圖，是我為了回信順手畫的。畫到一半卡住了——因為我發現自己說不清楚 &lt;strong&gt;MWL 到底算誰的&lt;/strong&gt;。是 HIS 的一部分？還是 PACS 的功能？&lt;/p&gt;
&lt;p&gt;去查了才確定：它是&lt;strong&gt;獨立的 DICOM 服務&lt;/strong&gt;，資料源頭來自 HIS，但講的是 DICOM 的語言，所以通常跟 PACS 擺在一起。我在那之前的理解一直是糊的，只是因為沒人問我，所以我也沒發現自己不懂。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;能不能畫出來，是理解程度最誠實的測驗。&lt;/strong&gt; 名詞可以背，但要把箭頭畫在哪一格，你騙不了自己。（難怪架構師面試很愛考圖解系統設計）&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/images/og/hospital-integration-glossary-ae-title-pacs-his.png" medium="image"/><category>DICOM</category><category>PACS</category><category>HIS</category><category>醫療設備</category><category>Domain Knowledge</category><enclosure url="https://bobochen.dev/images/og/hospital-integration-glossary-ae-title-pacs-his.png" length="0" type="image/png"/></item><item><title>OWASP Top 10 2025 白話版：我丟給新人的第一份清單，也是我面試最愛問的題目</title><link>https://bobochen.dev/blog/owasp-top-10-2025-plain-guide/</link><guid isPermaLink="true">https://bobochen.dev/blog/owasp-top-10-2025-plain-guide/</guid><description>OWASP Top 10 睽違四年改版了。這篇用最口語的方式把 2025 版十個分類講一遍：每一項是什麼、真實世界長什麼樣、怎麼防，以及我面試時會怎麼問、想聽到什麼答案。附 2021 到 2025 的搬家對照圖、榜單排名方法論白話解讀，還有給新人的學習順序。</description><pubDate>Wed, 26 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;我面試最愛問的一題&quot;&gt;我面試最愛問的一題&lt;/h2&gt;
&lt;p&gt;「你聽過 OWASP Top 10 嗎？」&lt;/p&gt;
&lt;p&gt;九成的人會說聽過。接著我請他隨便講幾個，通常會得到「SQL Injection、XSS……嗯……CSRF？」然後就停在那裡。&lt;/p&gt;
&lt;p&gt;這個答案不能算錯，但它暴露了一件事：&lt;strong&gt;這個人是把 OWASP Top 10 當成一份漏洞名詞的口訣在背，而不是當成一張地圖在用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我真正想聽的是這種回答：「排第一的一直是存取控制失效，因為它幾乎沒辦法靠工具自動掃出來——工具不知道『這個 user 該不該看這筆訂單』，只有你的商業邏輯知道。」&lt;/p&gt;
&lt;p&gt;講得出這句話的人，我大概就知道他真的踩過坑。&lt;/p&gt;
&lt;p&gt;而現在時機正好：&lt;a href=&quot;https://owasp.org/Top10/2025/&quot;&gt;OWASP Top 10 睽違四年改版了&lt;/a&gt;，2025 版是這份清單的第八版，上一版還停在 2021。如果你手上那份筆記是 2021 的，有兩個分類是全新的、有一個舊分類整個被吃掉、還有幾個名字改了。這篇就是我拿來帶新人的那份講義。&lt;/p&gt;
&lt;h2 id=&quot;先講清楚它不是什麼&quot;&gt;先講清楚：它不是什麼&lt;/h2&gt;
&lt;p&gt;新人最容易誤會的三件事，我每次都要先講。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，它不是「十大漏洞」，是「十大風險分類」。&lt;/strong&gt; SQL Injection 不是 Top 10 的其中一項，它只是 A05 Injection 這個分類底下的一種。2025 版十個分類加起來一共收了 248 個 CWE（Common Weakness Enumeration，弱點類型的標準編號）。所以講「Top 10 有十個漏洞」是講錯的。&lt;/p&gt;
&lt;p&gt;官方在導論裡其實有回答為什麼不乾脆列十個 CWE 就好——因為不是每個 CWE 都存在於每種語言和框架，而且同一件事常常有好幾個 CWE 編號可以用（光是「injection」相關的就有一大票）。用分類才能把不同編號但同一種病因的東西收在一起。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，它不是完整清單。&lt;/strong&gt; MITRE 的 CWE 字典在這版發布時有 968 個條目，Top 10 只碰到其中 248 個。過了這十關不代表你安全，只代表你沒踩到最常見的坑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，它不是稽核標準。&lt;/strong&gt; 官方自己的定位是 awareness document，「意識文件」。你不能拿 Top 10 去做驗收——那是 ASVS（Application Security Verification Standard）的工作。Top 10 是入門地圖，ASVS 才是檢查表。&lt;/p&gt;
&lt;p&gt;面試時如果對方能主動把這三件事講出來，我通常就不用再往下問基礎題了。&lt;/p&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;這十條路分別踩在房子的哪個位置。點數字可以跳到那一項。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id=&quot;2025-版哪裡不一樣&quot;&gt;2025 版哪裡不一樣&lt;/h2&gt;
&lt;p&gt;先看變動，因為大家第一時間都最想知道新舊版有什麼差異。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/owasp-top-10-2025-plain-guide/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;用講的就是這幾件事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;兩個新面孔。&lt;/strong&gt; A03 軟體供應鏈失效，是把 2021 的「使用有已知漏洞的元件」整個擴大——不再只管你的 npm 套件有沒有 CVE，而是連建置系統、CI/CD、artifact 倉庫、開發者的筆電全都算進來。A10 例外狀況處理不當則是全新分類，講的是錯誤處理、邏輯錯誤、fail open 這一票事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一個被吃掉。&lt;/strong&gt; 2021 的 A10 SSRF 沒有消失，它被併進 A01 存取控制失效裡了（CWE-918 現在掛在 A01 底下）。這題面試很好用：問「SSRF 到 2025 版跑去哪了」，能答出「被歸到 A01，因為 SSRF 本質上就是繞過存取控制去打內網」的人，是真的理解而不是背的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三個改名。&lt;/strong&gt; A07 從「Identification and Authentication Failures」簡化成「Authentication Failures」；A08 從 Software &lt;strong&gt;and&lt;/strong&gt; Data 改成 Software &lt;strong&gt;or&lt;/strong&gt; Data；A09 從 Logging and &lt;strong&gt;Monitoring&lt;/strong&gt; 改成 Logging and &lt;strong&gt;Alerting&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;A09 這個改名最有內容。官方的說法是要強調 alerting——「great logging with no alerting is of minimal value」，只記 log 卻沒有告警機制，對於發現事件的價值微乎其微。這句話我覺得每個做維運的人都該貼在螢幕上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;四個換位。&lt;/strong&gt; Security Misconfiguration 從 #5 衝到 #2，Cryptographic Failures 從 #2 掉到 #4，Injection 從 #3 掉到 #5，Insecure Design 從 #4 掉到 #6。&lt;/p&gt;
&lt;p&gt;注意「掉」不等於「變安全了」。Injection 依然是所有分類裡 CVE 數量最多的（六萬多筆）。它往下掉，主要是因為別人升上來了。&lt;/p&gt;
&lt;h2 id=&quot;十個風險一個一個白話講&quot;&gt;十個風險，一個一個白話講&lt;/h2&gt;
&lt;p&gt;每一項我都用同一個格式：一句話、真實場景、怎麼防、面試會問什麼。&lt;/p&gt;
&lt;h3 id=&quot;a012025-存取控制失效broken-access-control&quot;&gt;A01:2025 存取控制失效（Broken Access Control）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像飯店房卡，刷得開別人的房間。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;這個人登入了，但他能碰的東西超過他該碰的範圍。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;蟬聯第一名，而且是壓倒性的——官方資料裡，受測應用有 100% 都被找出某種形式的存取控制問題，這個分類收了 40 個 CWE，是所有分類裡最多的。&lt;/p&gt;
&lt;p&gt;最經典的場景：你的 API 是 &lt;code&gt;/api/orders/1234&lt;/code&gt;，使用者把網址改成 &lt;code&gt;1235&lt;/code&gt;，就看到別人的訂單。這叫 IDOR（不安全的直接物件參照），現在的 API 安全圈更常叫它 BOLA。程式碼裡通常長這樣——查得到那筆訂單，但忘了檢查那筆訂單是不是屬於這個人。&lt;/p&gt;
&lt;p&gt;其他常見形態：把權限檢查寫在前端（攻擊者直接 &lt;code&gt;curl&lt;/code&gt; 就繞過了）、JWT 拿到手就信不驗簽、CORS 開太寬、目錄列表沒關讓人翻到 &lt;code&gt;.git&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;怎麼防，記三句：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;預設拒絕。&lt;/strong&gt; 除了公開資源，其他一律先擋，明確允許才放行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;權限檢查只寫一次，全站重用。&lt;/strong&gt; 每個 controller 各寫一份，遲早有一個會忘記。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一定要在伺服器端。&lt;/strong&gt; 前端的檢查是 UX，不是安全機制。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「你怎麼確認一個使用者只能看到自己的資料？」我想聽的是「查詢的時候就把 owner 條件帶進 WHERE，而不是查完再判斷」，以及「這種東西要寫進整合測試，用 B 的 token 去打 A 的資源，預期拿到 403」。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a022025-安全設定錯誤security-misconfiguration&quot;&gt;A02:2025 安全設定錯誤（Security Misconfiguration）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像新買的保險箱，密碼還停在 0000。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;程式沒寫錯，是你把開關打錯了。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;從第五名衝到第二名。官方的解釋很直白：軟體行為愈來愈多是靠設定決定的，設定變多，設錯的機會自然變多。同樣有 100% 的受測應用被找出某種設定問題。&lt;/p&gt;
&lt;p&gt;真實場景就是那些每年都上新聞的事：S3 bucket 設成公開、admin 帳號密碼還是 &lt;code&gt;admin/admin&lt;/code&gt;、production 開著 debug 模式把完整 stack trace 吐給使用者看、範例應用沒從正式機移除、雲端服務的分享權限預設對整個網際網路開放。&lt;/p&gt;
&lt;p&gt;怎麼防：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;硬化流程要可重複、要自動化。&lt;/strong&gt; dev / QA / production 的設定要一模一樣，只有帳密不同。手動設定的環境，第三台一定會歪。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最小平台。&lt;/strong&gt; 用不到的功能、範例、文件、port，通通移除，不是關掉而是移除。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要把 static key 塞進程式碼或設定檔。&lt;/strong&gt; 用 IAM role、短期憑證、identity federation。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「為什麼這一項今年會升到第二名？」答得出「因為現在應用的行為大量由設定驅動，IaC 和雲端把設定面放大了」就很到位。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a032025-軟體供應鏈失效software-supply-chain-failures&quot;&gt;A03:2025 軟體供應鏈失效（Software Supply Chain Failures）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像自己沒下毒，但買回來的醬油裡有。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;你自己的程式沒問題，但你信任的東西有問題。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今年最值得聊的一項。它在社群調查裡被投成第一名，而且是整整 50% 的人把它排 #1。&lt;/p&gt;
&lt;p&gt;它的前身是 2013 年的「使用有已知漏洞的元件」，2021 年叫 A06。但這次範圍整個擴大了：不只是「你的 lodash 有沒有 CVE」，而是整條鏈——你的 CI/CD、你的 artifact 倉庫、你的 container registry、你的 IDE 外掛、你開發機本身。&lt;/p&gt;
&lt;p&gt;官方在這一項舉的例子讀起來像驚悚片：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SolarWinds（2019）&lt;/strong&gt;：受信任的供應商被植入惡意程式，導致約 18,000 個組織連帶被入侵。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bybit（2025）&lt;/strong&gt;：15 億美元被偷，起因是錢包軟體的供應鏈攻擊，而且惡意程式只在特定條件下才發作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shai-Hulud（2025）&lt;/strong&gt;：第一個成功自我複製的 npm 蠕蟲。它在熱門套件裡塞惡意版本，用 post-install script 偷資料傳到公開的 GitHub repo，還會偵測受害環境裡的 npm token，自動拿去發布更多惡意套件。散播到五百多個套件版本才被 npm 阻斷。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Shai-Hulud 那個案例官方下了一句結論，我覺得是這整個分類的重點：&lt;strong&gt;它證明了開發者自己現在就是供應鏈攻擊的首要目標。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;還有個很反直覺的數據：這個分類對應的 CVE 少得可憐（只有十來筆），但一被測出來就是最高的平均發生率，而且平均的 exploit 與 impact 分數是十個分類裡最高的。少見、難測，但一中就是重傷。&lt;/p&gt;
&lt;p&gt;怎麼防：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;有 SBOM，而且要管到 transitive dependency。&lt;/strong&gt; 你直接裝的套件你知道，它裝的那些你通常不知道。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只從官方來源拿套件，優先用有簽名的。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD 的安全等級不能低於它建置的系統。&lt;/strong&gt; 這條最多人違反——production 有 MFA、有 IAM、有稽核，build server 卻是一把萬能 token 走天下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分批推送更新。&lt;/strong&gt; 用 staged rollout 或 canary，萬一供應商被攻陷，炸的不是全部。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「Log4Shell 那次你們怎麼處理的？」我不是要考 CVE 編號，我要知道：你多久之內盤點出「哪些服務用了 log4j、哪一版」。答不出來的團隊，通常是沒有 SBOM。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a042025-加密機制失效cryptographic-failures&quot;&gt;A04:2025 加密機制失效（Cryptographic Failures）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像把密碼寫在明信片背面寄出去。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;該加密的沒加密，有加密的用錯方法。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;從第二名掉到第四名，但別誤會，它的平均發生率仍有 3.80%，在十個分類裡排前段。&lt;/p&gt;
&lt;p&gt;具體長什麼樣：密碼用 MD5 或 SHA1 存（那不是密碼雜湊函數）、金鑰 commit 進 git、還在用 TLS 1.0、用 &lt;code&gt;Math.random()&lt;/code&gt; 產 token、IV 重複使用、ECB 模式、只加密不做認證加密。&lt;/p&gt;
&lt;p&gt;官方特別點名了一件事：這個分類裡最常見的 CWE 有好幾個都跟「亂數不夠亂」有關——弱的偽亂數產生器、熵不足、可預測的演算法。新人很容易在這裡翻車，因為 &lt;code&gt;random()&lt;/code&gt; 看起來就很隨機。&lt;/p&gt;
&lt;p&gt;怎麼防：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;密碼用 Argon2、scrypt、yescrypt 或 PBKDF2-HMAC-SHA-512 這類有 work factor 的雜湊函數。&lt;/strong&gt; 舊系統還在用 bcrypt 的，去查 OWASP 的 Password Storage Cheat Sheet。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;傳輸一律 TLS 1.2 以上&lt;/strong&gt;，並開 HSTS。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;永遠用認證加密（AEAD），不要只做加密。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不必要的敏感資料就別存。&lt;/strong&gt; 沒留的資料偷不走。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;還有一條 2025 版新加的，值得記一下：官方要求你現在就開始準備後量子密碼學（PQC），高風險系統最晚在 2030 年底前要完成。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「密碼要怎麼存？」聽到「加密存起來」就扣分——密碼是雜湊不是加密，因為你根本不需要還原它。能講出 salt 和 work factor 才算及格。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a052025-注入攻擊injection&quot;&gt;A05:2025 注入攻擊（Injection）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像在點餐單上寫「把收銀機給我」，店員真的照做。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;你把使用者打的字，當成指令執行了。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;從第三名掉到第五名，但它依然是 CVE 數量的冠軍，六萬多筆。裡面 XSS 就佔了三萬多，SQL Injection 一萬四千多。&lt;/p&gt;
&lt;p&gt;官方對這兩者的定位很有意思：XSS 是「高頻率、低衝擊」，SQL Injection 是「低頻率、高衝擊」。這兩句話拿來面試講，比背定義有用得多。&lt;/p&gt;
&lt;p&gt;Injection 不只有 SQL，還有 NoSQL、OS command、ORM、LDAP、Expression Language。原理都一樣：資料跑進了指令的位置。&lt;/p&gt;
&lt;p&gt;2025 版還加了一句，我覺得是這版最有時代感的一行字：&lt;strong&gt;類似的注入問題在 LLM 上已經很常見了，但那部分請看 OWASP 的 LLM Top 10，特別是 LLM01:2025 Prompt Injection。&lt;/strong&gt; 也就是說，官方明確把 prompt injection 劃到另一份清單去了，別在這裡找它。&lt;/p&gt;
&lt;p&gt;怎麼防，核心只有一句：&lt;strong&gt;把資料和指令分開。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用參數化查詢或 ORM，別做字串拼接。&lt;/li&gt;
&lt;li&gt;注意：&lt;strong&gt;stored procedure 不等於安全。&lt;/strong&gt; 如果它裡面用 &lt;code&gt;EXECUTE IMMEDIATE&lt;/code&gt; 或 &lt;code&gt;exec()&lt;/code&gt; 拼字串，一樣會中。&lt;/li&gt;
&lt;li&gt;白名單式的伺服器端驗證是輔助，不是主要防線。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「用了 ORM 是不是就不會 SQL Injection？」正確答案是不一定。官方就舉了 Hibernate HQL 的例子——只要你在裡面拼字串，一樣中招。這題很能篩掉「我聽說 ORM 很安全」的人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a062025-不安全的設計insecure-design&quot;&gt;A06:2025 不安全的設計（Insecure Design）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像蓋了金庫，設計圖上卻沒有「鎖」這個東西。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;這不是寫錯，是一開始就想錯。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;從第四名掉到第六名，而官方認為這是好消息——威脅建模的普及和 Secure by Design 的推廣，確實看到了改善。&lt;/p&gt;
&lt;p&gt;這一項最需要跟新人解釋清楚的，是它和「實作缺陷」的差別。官方講得很好：&lt;strong&gt;完美的實作救不了不安全的設計，因為那些該有的防護，你從頭到尾就沒設計過。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;官方舉的三個例子都不是技術漏洞，全是商業邏輯：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用「密碼提示問題」做身分找回。這已經被 NIST 800-63b 明文禁止了，理由很簡單：那些答案不只你知道。&lt;/li&gt;
&lt;li&gt;電影院連鎖店開放團體訂票折扣，上限十五人才要押金。攻擊者用幾個 request 就一次訂走六百個座位、把所有廳都佔滿。&lt;/li&gt;
&lt;li&gt;電商沒有防機器人機制，顯示卡一上架就被黃牛程式掃光。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看出來了嗎？這三件事沒有一個是程式碼寫錯，全都是「需求階段沒人想過會被這樣玩」。&lt;/p&gt;
&lt;p&gt;怎麼防：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;威脅建模。&lt;/strong&gt; 至少對認證、存取控制、商業邏輯這些關鍵流程做。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;建立安全設計模式庫&lt;/strong&gt;，或者說 paved road——讓做對的路變成最好走的路。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寫 misuse case，不只寫 use case。&lt;/strong&gt; 除了「使用者會怎麼用」，也要寫「壞人會怎麼用」。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「設計缺陷和實作缺陷差在哪？」這題答得漂亮的人很少，但答得出來的通常是資深的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a072025-身分驗證失效authentication-failures&quot;&gt;A07:2025 身分驗證失效（Authentication Failures）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像警衛只看名牌上的名字，不看臉。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;系統把不該相信的人，當成合法使用者了。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;穩坐第七名，名字從「Identification and Authentication Failures」簡化了。官方的觀察是：標準化的驗證框架普及，確實有幫助——但這一項還是沒有下降。&lt;/p&gt;
&lt;p&gt;常見形態：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Credential stuffing。&lt;/strong&gt; 攻擊者拿外洩的帳密清單來試。現在還演化出混合式攻擊——用外洩密碼的變體去猜，例如 &lt;code&gt;Password1!&lt;/code&gt;、&lt;code&gt;Password2!&lt;/code&gt;、&lt;code&gt;Password3!&lt;/code&gt; 這樣遞增。&lt;/li&gt;
&lt;li&gt;允許 &lt;code&gt;Password1&lt;/code&gt; 這種弱密碼、允許已知外洩的密碼。&lt;/li&gt;
&lt;li&gt;session id 出現在 URL 裡、登入成功後沒換新的 session id（session fixation）。&lt;/li&gt;
&lt;li&gt;登出後 token 沒失效。&lt;/li&gt;
&lt;li&gt;「密碼提示問題」——又是它，A06 也點名過一次。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼防：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;能上 MFA 就上 MFA。&lt;/strong&gt; 這一條抵得過其他所有條加起來。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鼓勵使用者用密碼管理器。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新密碼要去比對外洩資料庫&lt;/strong&gt;（例如 haveibeenpwned.com）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不要強迫定期換密碼&lt;/strong&gt;，除非你懷疑外洩了。這條寫在 NIST 800-63b 裡，但台灣還是一堆系統在每 90 天逼人換密碼——結果就是 &lt;code&gt;Password9!&lt;/code&gt; 變成 &lt;code&gt;Password10!&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;註冊、找回密碼、API 路徑要防帳號列舉：不管哪種失敗，都回同一句「帳號或密碼錯誤」。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「為什麼不該強迫使用者每三個月換密碼？」這題有點反直覺，而且能直接看出對方讀的是舊觀念還是新規範。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a082025-軟體或資料完整性失效software-or-data-integrity-failures&quot;&gt;A08:2025 軟體或資料完整性失效（Software or Data Integrity Failures）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像宅配包裹被拆開換過，你連膠帶都沒看就簽收。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;你沒驗證那個東西有沒有被動過手腳，就直接信了。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;維持第八名。新人最常搞混的是它跟 A03 供應鏈的差別，官方自己有劃線：&lt;strong&gt;A08 談的是更低層次的「有沒有驗證完整性」，A03 談的是整條供應鏈的失效。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我自己的分法是：A03 是「你從哪裡拿到這個東西」，A08 是「你有沒有檢查它路上有沒有被換掉」。&lt;/p&gt;
&lt;p&gt;典型場景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;韌體更新不驗簽章。家用路由器、機上盒大量中招，而且往往沒有補救手段，只能等舊版自然淘汰。&lt;/li&gt;
&lt;li&gt;CI/CD 從不受信任的地方拉 artifact，拉完不驗就用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不安全的反序列化。&lt;/strong&gt; 官方那個例子很生動：React 前端呼叫 Spring Boot 微服務，工程師為了 immutable，把使用者狀態序列化後在每個 request 之間傳來傳去。攻擊者看到 base64 開頭是 &lt;code&gt;rO0&lt;/code&gt;（Java 物件的特徵），直接拿反序列化掃描工具打到 RCE。&lt;/li&gt;
&lt;li&gt;為了方便，把 &lt;code&gt;myCompany.SupportProvider.com&lt;/code&gt; 做 DNS 對應到 &lt;code&gt;support.myCompany.com&lt;/code&gt;。結果所有 &lt;code&gt;myCompany.com&lt;/code&gt; 網域的 cookie，包含驗證 cookie，全部送給了外部支援廠商。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼防：用數位簽章驗證來源與完整性、只從受信任的 repository 取用、程式碼和設定變更都要 review、序列化資料一定要有完整性檢查。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「A03 跟 A08 差在哪？」問這題不是刁難，是看對方是不是真的讀過 2025 版——因為 2021 版沒這個對照關係。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a092025-安全記錄與告警失效security-logging-and-alerting-failures&quot;&gt;A09:2025 安全記錄與告警失效（Security Logging and Alerting Failures）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像監視器有裝，但沒插電。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;被打了，而你不知道。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;維持第九名，改名的重點在 alerting。官方那句話值得再貼一次：只記 log 卻沒有告警，對於發現事件的價值微乎其微。&lt;/p&gt;
&lt;p&gt;這一項有個很特別的地方：它在資料上永遠會被低估——只有 723 筆 CVE 對應，因為這種東西根本很難用工具測。它是靠社群調查投票才進榜的，而且&lt;strong&gt;這已經是第三次靠投票進來了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;官方舉的三個案例都很痛：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;某兒童健保業者的網站沒有監控和記錄，是外部通報才知道有攻擊者存取並修改了超過 350 萬名兒童的健康紀錄。事後檢討發現，這個外洩可能從 2013 年就開始了，持續超過七年。&lt;/li&gt;
&lt;li&gt;某印度大型航空公司外洩超過十年份的旅客個資，含護照和信用卡資料。事發在第三方雲端託管商，而託管商過了一段時間才通知航空公司。&lt;/li&gt;
&lt;li&gt;某歐洲大型航空公司被攻擊者收割四十多萬筆付款紀錄，最後被隱私主管機關罰了兩千萬英鎊。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我特別喜歡官方提的一個防禦技巧，叫 &lt;strong&gt;honeytoken&lt;/strong&gt;：在資料庫裡放一些正常業務絕對不會碰到的假資料或假帳號，任何人碰到它就發告警。因為正常情況下沒人會存取，所以誤報率幾乎是零。&lt;/p&gt;
&lt;p&gt;其他要點：所有登入、存取控制、輸入驗證的失敗都要記；每個有安全控制的地方，成功和失敗都要記；log 本身要防竄改（append-only）；log 不要記到 PII；還有一條很容易忽略——&lt;strong&gt;log 也要做編碼處理&lt;/strong&gt;，不然攻擊者可以注入你的 log 系統（CWE-117）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「你的系統被打了，你多久會知道？誰會知道？」這題沒有標準答案，但答「應該有 log 吧」跟答「登入失敗超過閾值會進 Slack 的 oncall 頻道」是兩種人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;a102025-例外狀況處理不當mishandling-of-exceptional-conditions&quot;&gt;A10:2025 例外狀況處理不當（Mishandling of Exceptional Conditions）&lt;/h3&gt;
&lt;figure class=&quot;owasp-fig&quot;&gt;&lt;figcaption&gt;就像停電的時候，電子鎖自動全開。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;blockquote&gt;
&lt;p&gt;一句話：&lt;strong&gt;出事的時候，系統不知道自己該做什麼。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;2025 年的全新分類，收了 24 個 CWE。官方說這裡面有些 CWE 以前被歸在「程式碼品質不佳」，但那個標籤太籠統了，拆出來才有辦法給具體建議。&lt;/p&gt;
&lt;p&gt;它包含三種失敗，可能同時發生：&lt;strong&gt;沒有預防異常發生、沒有察覺異常正在發生、發生之後反應不當或根本沒反應。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;官方對它下了一個我很喜歡的定義：&lt;strong&gt;任何時候應用程式不確定自己下一步該做什麼，就是一次例外狀況處理不當。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;三個攻擊場景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;資源耗盡。&lt;/strong&gt; 檔案上傳有接住例外，但沒有釋放資源。每一次例外都鎖住一點資源，最後全部耗光，變成 DoS。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;敏感資訊外洩。&lt;/strong&gt; 資料庫錯誤直接把完整錯誤訊息吐給使用者。攻擊者於是刻意一直觸發錯誤，用洩漏的系統資訊拼出一個更精準的 SQL Injection。錯誤訊息成了偵察工具。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;狀態損毀。&lt;/strong&gt; 多步驟金流交易被網路中斷打斷。如果順序是「扣款、入帳、記錄交易」而系統沒有正確 rollback，攻擊者可能把帳戶掏空，或利用競態條件讓同一筆錢送出好幾次。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼防：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;在錯誤發生的地方就接住它&lt;/strong&gt;，不要放到最外層才統一處理。同時要有全域 exception handler 當最後一道網。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fail closed，不要 fail open。&lt;/strong&gt; 交易做到一半出事，整筆 rollback 重來。官方特別提醒：&lt;strong&gt;試圖從交易中途「救回來」，往往就是製造不可逆錯誤的地方。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;凡是能設限的都要設限&lt;/strong&gt;——rate limit、資源配額、throttling。官方那句話很有畫面：資訊系統裡不該有任何東西是無限的，否則你會換來服務中斷、暴力破解成功，還有一張驚人的雲端帳單。&lt;/li&gt;
&lt;li&gt;整個組織用同一套方式處理例外，這樣 code review 才審得動。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;面試我會問&lt;/strong&gt;：「你的 API 掛掉的時候，是擋住還是放行？」很多系統的驗證服務逾時之後選擇放行，理由是「不能影響使用者體驗」。那就是 fail open，也就是 CWE-636。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;這份榜單是怎麼排出來的&quot;&gt;這份榜單是怎麼排出來的&lt;/h2&gt;
&lt;p&gt;這題是我留給資深候選人的。能講清楚方法論的人，通常對「資料能證明什麼、不能證明什麼」有感覺。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/owasp-top-10-2025-plain-guide/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;重點在那個 &lt;strong&gt;8 + 2&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;十個名額裡只有八個是照資料排的，另外兩個開放給社群問卷投票。官方給的理由我覺得非常誠實：&lt;strong&gt;「檢視貢獻的資料，本質上是在看過去。」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;新的弱點被發現、被寫成測試方法、被整合進工具、再被跑過夠大量的應用——這中間可能要好幾年。等到資料看得見，事情早就發生很久了。而且有些風險可能永遠測不出來（A09 就是典型）。所以要靠第一線的人投票，把資料裡看不到的東西補進來。&lt;/p&gt;
&lt;p&gt;官方形容自己是 &lt;strong&gt;data-informed, but not blindly data-driven&lt;/strong&gt;——參考資料，但不盲從資料。&lt;/p&gt;
&lt;p&gt;還有幾個細節值得知道：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;這次不用 CVSS v4.0。&lt;/strong&gt; 因為 v4 的計分演算法整個改了，不再像 v2、v3 那樣直接給出 Exploit 和 Impact 分數。官方說會想辦法在未來的版本處理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只算「有沒有」，不算「有幾個」。&lt;/strong&gt; 一個應用裡某個 CWE 出現 4 次還是 4000 次，對排名沒差別。因為人工測試通常同一種問題只記一次，自動化工具卻會每一處都記一筆——算次數只會扭曲真實的普及率。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每個分類最多收 40 個 CWE&lt;/strong&gt;，這是刻意設的上限。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;順帶一提，如果你去對照官方導論頁和各分類的詳細頁，會發現有幾個數字對不太起來（例如 A03 的 CWE 數量和平均發生率、A05 的 CWE 數量，兩邊寫的不一樣）。這不影響結論，但也剛好說明一件事：&lt;strong&gt;面試不會考你背這些數字，考的是你懂不懂它們代表什麼。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;一張圖它們分別踩在哪一段&quot;&gt;一張圖：它們分別踩在哪一段&lt;/h2&gt;
&lt;p&gt;新人最常見的困擾是「十個分類記不住，因為它們感覺是同一層的東西」。其實不是。把一個 request 的生命週期攤開來看，它們各自站在不同的位置：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/owasp-top-10-2025-plain-guide/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這張圖我畫給新人看的時候，通常會補一句：&lt;strong&gt;A06 在最前面，A09 在最後面，這兩個是最容易被跳過的。&lt;/strong&gt; 因為它們一個發生在寫程式之前，一個發生在事情結束之後，都不在「寫功能」的那段時間裡。&lt;/p&gt;
&lt;h2 id=&quot;我實際會問的面試題&quot;&gt;我實際會問的面試題&lt;/h2&gt;
&lt;p&gt;由淺到深，附上我想聽到的東西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 「隨便挑一個你最有感的，講講你踩過的坑。」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;開放題，用來破冰。我看的是他講的是自己的經驗還是課本的例子。講得出「那次是因為我們的 API 忘了驗 owner」的人，遠勝於背出十個名詞的人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 「為什麼存取控制失效一直是第一名？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我想聽到的關鍵字是「工具測不出來」。掃描器可以找 SQL Injection 的 pattern，但它不知道 user 42 該不該看訂單 1234——那是你的商業邏輯，只有你知道。所以這一類只能靠設計和測試，不能靠買工具解決。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 「SSRF 在 2025 版跑去哪了？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;考的是有沒有跟上改版。答案是併進 A01。追問「為什麼合理」，好的回答是：SSRF 本質上就是讓伺服器替你去存取你原本碰不到的資源，那是存取控制問題。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 「用了 ORM 是不是就不會 SQL Injection？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;答「是」就是紅旗。正確答案是不一定，只要在裡面拼字串一樣會中，官方就拿 Hibernate HQL 舉例。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 「你的服務依賴的某個套件爆了 RCE，你第一件事做什麼？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我要聽的不是「趕快升級」，是「先盤點哪些服務用了它、哪一版、有沒有 transitive 帶進來的」。這題直接測有沒有 SBOM。答得出 staged rollout、虛擬修補的加分。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. 「你的驗證服務逾時了，這個 request 你放不放行？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;fail open vs fail closed。這題沒有絕對答案，但我要聽他有意識到這是個安全決策，而不是隨口說「當然要放行啊不然使用者會抱怨」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7.（給資深的）「這份榜單怎麼排出來的？為什麼有兩個名額是投票決定的？」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;答得出 8 + 2、答得出「資料只能反映過去、有些風險永遠測不出來」的人，通常對資安的理解是有厚度的。&lt;/p&gt;
&lt;h2 id=&quot;給新人的學習順序&quot;&gt;給新人的學習順序&lt;/h2&gt;
&lt;p&gt;我不會叫新人「把 Top 10 讀完」。那個讀完也記不住。我給的順序是這樣：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一週：先動手打一次。&lt;/strong&gt; 去玩 &lt;a href=&quot;https://owasp.org/www-project-juice-shop/&quot;&gt;OWASP Juice Shop&lt;/a&gt;——一個故意寫得很爛的購物網站，你的任務是把它打穿。先體驗過「改個網址就看到別人資料」的那個瞬間，後面讀文件才有畫面。想要更循序漸進的可以用 WebGoat。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二週：讀 Top 10 本體，但只讀 Description 和 Example Attack Scenarios。&lt;/strong&gt; &lt;a href=&quot;https://owasp.org/Top10/2025/&quot;&gt;官方頁面&lt;/a&gt;每一項都有攻擊情境，那才是最好讀的部分。CWE 清單先跳過，那是查表用的，不是拿來讀的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三週開始：改讀 &lt;a href=&quot;https://cheatsheetseries.owasp.org/&quot;&gt;Cheat Sheet Series&lt;/a&gt;。&lt;/strong&gt; 這是我認為 OWASP 最被低估的資源。Top 10 告訴你「有這個問題」，Cheat Sheet 告訴你「這個語言這個框架該怎麼寫」。密碼儲存、輸入驗證、REST 安全、JWT，每一篇都是一頁式的實作指南。做開發的人，其實花在這裡的時間應該比 Top 10 多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;之後看方向分岔：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要做驗收、要寫安全需求 → &lt;a href=&quot;https://owasp.org/www-project-application-security-verification-standard/&quot;&gt;ASVS&lt;/a&gt;，這才是檢查表。&lt;/li&gt;
&lt;li&gt;要做測試 → &lt;a href=&quot;https://owasp.org/www-project-web-security-testing-guide/&quot;&gt;Web Security Testing Guide&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;要寫程式的防護基本功 → &lt;a href=&quot;https://owasp.org/www-project-proactive-controls/&quot;&gt;Proactive Controls&lt;/a&gt;，它是「主動該做什麼」，和 Top 10 的「不要做什麼」互補。&lt;/li&gt;
&lt;li&gt;做 API 的 → OWASP API Security Top 10，那是另一份清單。&lt;/li&gt;
&lt;li&gt;碰 LLM 的 → OWASP LLM Top 10，prompt injection 在那裡不在這裡。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一句話總結我的建議：&lt;strong&gt;Top 10 拿來建立地圖，Cheat Sheet 拿來實際幹活，ASVS 拿來驗收。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;冷知識那個-w-已經不是-web-了&quot;&gt;冷知識：那個 W 已經不是 Web 了&lt;/h2&gt;
&lt;p&gt;大部分人（包括我，很長一段時間）都以為 OWASP 是 Open &lt;strong&gt;Web&lt;/strong&gt; Application Security Project。&lt;/p&gt;
&lt;p&gt;但你現在去看&lt;a href=&quot;https://owasp.org/about/&quot;&gt;官方的 About 頁面&lt;/a&gt;，寫的是 &lt;strong&gt;The Open Worldwide Application Security Project&lt;/strong&gt;。W 從 Web 換成了 Worldwide。原因也很直觀：這個基金會現在管的東西早就不只是網頁——行動應用、API、雲端、IoT、機器學習模型，全都在射程內。&lt;/p&gt;
&lt;p&gt;順帶一提，同一個頁面寫著 OWASP Foundation 在 &lt;strong&gt;2001 年 12 月 1 日&lt;/strong&gt;啟動，2004 年 4 月 21 日正式登記為美國的非營利慈善組織。所以 Top 10 這份清單，比很多正在讀這篇文章的工程師還要老。&lt;/p&gt;
&lt;h2 id=&quot;最後&quot;&gt;最後&lt;/h2&gt;
&lt;p&gt;如果這篇你只記得一件事，我希望是這個：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OWASP Top 10 不是拿來背的，是拿來當成提問清單的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;下次你在做 code review 或設計評估的時候，把這十個當成十個問題問一遍：這個 endpoint 有驗 owner 嗎？這個設定 production 跟 staging 一樣嗎？這個套件哪來的？這裡出錯的時候會 fail open 嗎？出事我們幾分鐘會知道？&lt;/p&gt;
&lt;p&gt;問完這十題，你就已經比大多數團隊做得多了。&lt;/p&gt;
&lt;p&gt;至於面試——我從來不在意有沒有人能把十項按順序背出來。我在意的是，當我問「這個功能你會怎麼確認它安全」的時候，對方腦子裡有沒有一張地圖。&lt;/p&gt;
&lt;p&gt;有地圖的人，我就知道可以放心把東西交給他。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/images/og/owasp-top-10-2025-plain-guide.png" medium="image"/><category>OWASP</category><category>資安</category><category>Application Security</category><category>面試</category><category>Web Security</category><category>新人培訓</category><category>CWE</category><enclosure url="https://bobochen.dev/images/og/owasp-top-10-2025-plain-guide.png" length="0" type="image/png"/></item><item><title>多個 agent 同時跑，你要怎麼管理？</title><link>https://bobochen.dev/blog/herdr-multi-agent-terminal-runtime/</link><guid isPermaLink="true">https://bobochen.dev/blog/herdr-multi-agent-terminal-runtime/</guid><description>Herdr 是用 Rust 寫的 coding agent runtime：背景 server 抓著真正的終端機，關了筆電 agent 照跑，還會直接告訴你哪一隻卡住在等你。附三隻 agent 同時跑的實測截圖，以及社群「後來為什麼不用了」那些抱怨的逐條驗證。</description><pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;一個很蠢但每天都在發生的場景&quot;&gt;一個很蠢但每天都在發生的場景&lt;/h2&gt;
&lt;p&gt;我這陣子的桌面大概長這樣。&lt;/p&gt;
&lt;p&gt;一個 Claude Code 在改部落格的樣式，一個 Codex 在跑資料回填，第三個分頁裡還有一隻 agent。前兩隻我知道在幹嘛，第三隻我以為它在跑。&lt;/p&gt;
&lt;p&gt;結果它在等我。&lt;/p&gt;
&lt;p&gt;它三十分鐘前就跳出「要不要 deploy 到 staging？1. Yes 2. No」，然後就停在那裡。我在另外兩個分頁忙進忙出，完全沒發現。三十分鐘，不是它在算，是它在等我按 Enter。&lt;/p&gt;
&lt;p&gt;這件事我一週要遇到好幾次。而我「解決」它的方式，說出來有點好笑：每隔一陣子就用 &lt;kbd&gt;Cmd&lt;/kbd&gt;+&lt;kbd&gt;1~9&lt;/kbd&gt; 把所有分頁掃一遍，看看有沒有人在等我。&lt;/p&gt;
&lt;p&gt;人肉輪詢。我變成了一個 polling loop。&lt;/p&gt;
&lt;h2 id=&quot;問題其實有三個不是一個&quot;&gt;問題其實有三個，不是一個&lt;/h2&gt;
&lt;p&gt;同時跑多隻 agent 之後，痛的地方分得很開：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，你的筆電是它們的生命線&lt;/strong&gt;。agent 跑在你的終端機裡，你關蓋、網路斷掉、SSH 掉線，那個 process 就沒了。一個跑了兩小時的任務，因為你要出門而中斷，這件事本身就很荒謬——它明明是台機器在跑，卻要你陪著。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，你不知道誰卡住了&lt;/strong&gt;。前面那個場景。終端機分頁只會告訴你「這裡有個 process」，不會告訴你「這隻在等你」。要知道狀態，只能一個一個切過去用眼睛看。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，agent 之間互相不認識&lt;/strong&gt;。你想讓 A 寫完之後叫 B 來 review，現在的做法通常是你當中間人：複製 A 的輸出，切到 B 的視窗，貼上去。你變成兩隻 agent 之間的 message queue。&lt;/p&gt;
&lt;p&gt;這三件事，Herdr 想一次處理掉。&lt;/p&gt;
&lt;h2 id=&quot;herdr-是什麼它不是一個-app&quot;&gt;Herdr 是什麼：它不是一個 app&lt;/h2&gt;
&lt;p&gt;先講最容易搞混的地方，因為這決定了它跟其他工具的差別。&lt;/p&gt;
&lt;p&gt;大部分「管 agent」的工具是 app：你開一個視窗，agent 在那個視窗裡跑，視窗關掉、程式當掉，工作就跟著沒了。&lt;/p&gt;
&lt;p&gt;Herdr 走的是另一條路。它&lt;a href=&quot;https://herdr.dev/&quot;&gt;官網&lt;/a&gt;自己的講法是「the runtime your coding agents live on」，agent 住在上面的那個 runtime。實際上是一個背景 server 持有真正的終端機（真的 PTY，不是模擬），agent 活在裡面；而所有 UI，包含它自己附的那個 TUI，都只是「連上去看」的 client。&lt;/p&gt;
&lt;p&gt;這個分法的重點，是那條虛線畫在哪裡：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/herdr-multi-agent-terminal-runtime/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;實線是「持有」，虛線是「連上去看」。你平常盯著的那個 TUI 在虛線那一側，所以它斷線、當掉、被你整個關掉，agent 都不知道發生了什麼事——抓著它們的是 server，不是你看到的那個視窗。&lt;/p&gt;
&lt;p&gt;換成現在的做法，這張圖會塌成一條直線：終端機視窗直接抓著 agent，視窗一關，下面全部一起死。&lt;/p&gt;
&lt;p&gt;它的比較頁面上有一句話我覺得整篇最值得記：真正能把這類工具分類的問題，是 &lt;strong&gt;當你正在看的那個東西消失時，agent 會怎樣&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;有感的三件事&quot;&gt;有感的三件事&lt;/h2&gt;
&lt;p&gt;下面三張圖都是我實際裝起來跑出來的畫面：同一個 session 裡兩個 workspace、三隻 agent（兩隻 Codex 一隻 Claude Code），加上一個真的 Astro dev server。不是官網的示意圖。&lt;/p&gt;
&lt;h3 id=&quot;一關筆電不會死&quot;&gt;一、關筆電不會死&lt;/h3&gt;
&lt;p&gt;安裝完之後在專案目錄下打 &lt;code&gt;herdr&lt;/code&gt;，它會啟動（或接上）背景 session，然後你就在裡面跑你原本的 agent。&lt;/p&gt;
&lt;p&gt;要走的時候按 &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;b&lt;/kbd&gt; 放開再按 &lt;kbd&gt;q&lt;/kbd&gt;，detach。或者更暴力一點，直接把終端機視窗關掉，一樣。回來再打一次 &lt;code&gt;herdr&lt;/code&gt; 就接回原本的畫面。&lt;/p&gt;
&lt;p&gt;真正要停掉所有東西，是 &lt;code&gt;herdr server stop&lt;/code&gt;。這個區分很重要——「我不看了」跟「我不跑了」是兩件事，一般終端機沒有這個區分。&lt;/p&gt;
&lt;p&gt;我實際驗過一次：三隻 agent 跑著、dev server 跑著，然後我把整個終端機 app 直接 quit 掉，再從外面用 CLI 問：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ herdr status
server:
  status: running
  version: 0.8.2
  socket: /Users/bobochen/.config/herdr/herdr.sock

$ herdr agent list
  blog      claude  idle     w2:p1
  critic    codex   idle     w2:p3
  reviewer  codex   idle     w3:p1

$ curl -s -o /dev/null -w &quot;%{http_code}&quot; http://localhost:4323/
200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;終端機已經不在了，三隻 agent 還在，pane 裡那個 dev server 也還在服務。重新開一個終端機打 &lt;code&gt;herdr&lt;/code&gt;，畫面長這樣：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;版面、每個 pane 的歷史輸出、agent 的對話紀錄，全部原樣回來。&lt;/p&gt;
&lt;p&gt;遠端也吃得下。SSH 到 VPS 上跑 &lt;code&gt;herdr&lt;/code&gt;，行為跟 tmux 一樣；或者用 &lt;code&gt;herdr --remote user@host&lt;/code&gt;，它會自己在對面裝好，你本機只是個薄 client。&lt;/p&gt;
&lt;h3 id=&quot;二它會直接告訴你誰在等你&quot;&gt;二、它會直接告訴你誰在等你&lt;/h3&gt;
&lt;p&gt;這是我最想要的功能，也是我這次實測最有感的一個。&lt;/p&gt;
&lt;p&gt;Herdr 會去讀每個 pane 的畫面，判斷裡面那隻 agent 現在是什麼狀態，然後在側邊欄標出來：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/herdr-multi-agent-terminal-runtime/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;blocked&lt;/code&gt; 就是「它在等你」，&lt;code&gt;done&lt;/code&gt; 是「背景跑完了但你還沒看到」。這兩個狀態一標出來，開頭那個三十分鐘的蠢事就不會再發生了。&lt;/p&gt;
&lt;p&gt;實際跑起來是這樣。我故意讓 Claude Code 去做一件需要授權的事，同時讓另一個 workspace 的 Codex 跑一份唯讀分析：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;重點在左邊那兩個圓點。我沒有切過去任何一個 pane，光看側邊欄就知道：粉紅色那個在等我，黃色那個還在跑。開頭那個場景要的就是這個。&lt;/p&gt;
&lt;p&gt;它的 agent guide 裡對 &lt;code&gt;unknown&lt;/code&gt; 有一句話寫得很老實：&lt;code&gt;unknown&lt;/code&gt; 表示認得出 pane 裡有 agent，但無法有把握地分類，&lt;strong&gt;它不代表任務完成&lt;/strong&gt;。會把自己判斷不準的地方寫進文件的工具，我通常比較信得過。&lt;/p&gt;
&lt;p&gt;順帶一提，這裡有個小細節值得注意：從 CLI 讀狀態不會把 &lt;code&gt;done&lt;/code&gt; 標記成「已看過」，只有你真的把那個 tab 切到前景才算。所以你不能用「狀態變 idle」來當自動化的完成訊號騙自己。&lt;/p&gt;
&lt;h3 id=&quot;三agent-可以自己開-pane自己叫-agent&quot;&gt;三、agent 可以自己開 pane、自己叫 agent&lt;/h3&gt;
&lt;p&gt;這部分是我覺得 Herdr 跟純多工終端機真正拉開距離的地方。&lt;/p&gt;
&lt;p&gt;Herdr 的 CLI 跟 socket API 是同一套介面，agent 自己就能用。它甚至有一份 &lt;a href=&quot;https://raw.githubusercontent.com/herdrdev/herdr/master/skills/herdr/SKILL.md&quot;&gt;SKILL.md&lt;/a&gt; 是專門寫給 agent 讀的，教它怎麼操作 Herdr。&lt;/p&gt;
&lt;p&gt;所以「A 寫完叫 B 來 review」這件事，可以變成 A 自己做：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/herdr-multi-agent-terminal-runtime/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;重點在 &lt;code&gt;--wait&lt;/code&gt; 那一段。它不是 sleep 幾秒然後賭一把，是真的等到對方的生命週期狀態穩定下來。而且 API 設計得滿謹慎的：如果目標 agent 正卡在確認框，&lt;code&gt;agent prompt&lt;/code&gt; 會直接回 &lt;code&gt;agent_blocked&lt;/code&gt; 拒絕送出，而不是把文字硬塞進一個正在問「Yes/No」的對話框裡——後者是你自己寫腳本控 agent 時最容易踩的坑。&lt;/p&gt;
&lt;p&gt;另外 &lt;code&gt;--no-focus&lt;/code&gt; 這個 flag 也很體貼：背景幫你開的東西不會把你的游標搶走。&lt;/p&gt;
&lt;p&gt;這串我自己用 CLI 從外面跑了一遍——就是上面那張圖裡 agent A 會替你做的事：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ herdr pane split w2:p1 --direction right --ratio 0.5 --no-focus
{&quot;result&quot;:{&quot;pane&quot;:{&quot;pane_id&quot;:&quot;w2:p3&quot;, ...}}}

$ herdr agent start critic --kind codex --pane w2:p3
{&quot;result&quot;:{&quot;agent&quot;:{&quot;name&quot;:&quot;critic&quot;,&quot;agent&quot;:&quot;codex&quot;,&quot;agent_status&quot;:&quot;idle&quot;, ...}}}

$ herdr agent prompt critic &quot;簡述 src/components 底下的資料夾結構，唯讀分析。&quot; --wait
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;跑完之後，同一個 tab 裡就多了一隻不同廠牌的 agent：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;左邊 Claude Code、右邊 Codex、下面一個 dev server，三個都在同一個 herdr session 裡。要注意的是這三格不是「一個 app 畫出來的三個面板」，是三個真的終端機——所以 Codex 那格自己跑了一次 &lt;code&gt;npm install -g @openai/codex&lt;/code&gt; 自動更新、然後要求重啟，跟它在一般終端機裡的行為完全一樣。Herdr 沒有插手，它只是持有那個 pane。&lt;/p&gt;
&lt;h2 id=&quot;那它跟-tmux-差在哪&quot;&gt;那它跟 tmux 差在哪？&lt;/h2&gt;
&lt;p&gt;如果你已經在用 tmux 或 zellij，你可能會覺得前兩點聽起來很耳熟——持久化的終端機、detach、SSH 進去接回來，這些 tmux 二十年前就有了。&lt;/p&gt;
&lt;p&gt;沒錯。Herdr 自己也承認這份繼承，它的比較頁面寫得很直白：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;tmux 給了人類持久性，Herdr 保留了整份繼承：真正的 PTY、detach、SSH。它加上去的是多工器從來沒有的那部分——知道哪個 pane 是 agent、它是不是卡住了、以及怎麼「等」它而不是「輪詢」它。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;tmux ls&lt;/code&gt; 會告訴你有三個 session。哪一個在等你回答？它不知道，它也沒有理由知道，因為 tmux 面對的是「終端機」這個抽象，不是「agent」。&lt;/p&gt;
&lt;p&gt;用一句話總結這個差別：&lt;strong&gt;tmux 讓終端機活著，Herdr 讓 agent 被看見&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;還有一個實務差別：Herdr 是滑鼠優先的。點 pane、拖分割線、右鍵選單，全部可以用滑鼠來。prefix key 預設一樣是 &lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;b&lt;/kbd&gt;，但你可以完全不學快捷鍵就開始用。這對「知道 tmux 很好但一直懶得學」的人來說，門檻低很多。&lt;/p&gt;
&lt;h2 id=&quot;怎麼開始&quot;&gt;怎麼開始&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -fsSL https://herdr.dev/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Windows 是 &lt;code&gt;irm https://herdr.dev/install.ps1 | iex&lt;/code&gt;。不想 pipe to shell 的話，&lt;code&gt;brew install herdr&lt;/code&gt;、&lt;code&gt;mise use -g herdr&lt;/code&gt;，或直接去 GitHub Releases 抓 binary 都可以。單一執行檔，macOS / Linux / Windows 都有。&lt;/p&gt;
&lt;p&gt;然後：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ~/your-project
herdr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;進去之後就跑你原本的 agent，&lt;code&gt;claude&lt;/code&gt;、&lt;code&gt;codex&lt;/code&gt;、隨便哪個，Herdr 會自己認出來並在側邊欄標狀態。官方說開箱偵測 20 種 agent CLI。&lt;/p&gt;
&lt;p&gt;幾個第一天會用到的：&lt;/p&gt;

































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;動作&lt;/th&gt;&lt;th&gt;怎麼做&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;往右／往下切 pane&lt;/td&gt;&lt;td&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;b&lt;/kbd&gt; 然後 &lt;kbd&gt;v&lt;/kbd&gt; ／ &lt;kbd&gt;-&lt;/kbd&gt;，或右鍵選單&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;開新 tab&lt;/td&gt;&lt;td&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;b&lt;/kbd&gt; 然後 &lt;kbd&gt;c&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;離開但讓它繼續跑&lt;/td&gt;&lt;td&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;b&lt;/kbd&gt; 然後 &lt;kbd&gt;q&lt;/kbd&gt;，或直接關視窗&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;接回來&lt;/td&gt;&lt;td&gt;&lt;code&gt;herdr&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;看所有快捷鍵&lt;/td&gt;&lt;td&gt;&lt;kbd&gt;Ctrl&lt;/kbd&gt;+&lt;kbd&gt;b&lt;/kbd&gt; 然後 &lt;kbd&gt;?&lt;/kbd&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;真的全部停掉&lt;/td&gt;&lt;td&gt;&lt;code&gt;herdr server stop&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;想讓你的 agent 自己會操作 Herdr，可以裝那份 skill：&lt;code&gt;npx skills add herdrdev/herdr --skill herdr -g&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;還有一招我覺得很聰明：官網提供一份 &lt;a href=&quot;https://herdr.dev/agent-guide.md&quot;&gt;agent-guide.md&lt;/a&gt;，你可以直接叫你的 agent 讀它，讓 agent 帶你做完設定。文件本身就是寫給 AI 讀的格式，裡面甚至有一條規則是「不要編造快捷鍵、設定欄位或 CLI flag」。工具作者知道自己的使用者有一半是 AI，這個時代感有點強烈。&lt;/p&gt;
&lt;h2 id=&quot;社群最常抱怨的三件事我一條一條試了&quot;&gt;社群最常抱怨的三件事，我一條一條試了&lt;/h2&gt;
&lt;p&gt;社群上關於 herdr 的討論其實不少，而且有個問題問得特別好：不是「好不好用」，而是「&lt;strong&gt;用過一陣子的人，後來為什麼不用了&lt;/strong&gt;」。&lt;/p&gt;
&lt;p&gt;這個問法比任何評測都誠實。我把底下被重複提到的抱怨挑出來，在自己這台機器上一條一條試。&lt;/p&gt;
&lt;h3 id=&quot;一切-workspace-沒辦法用-cmd數字&quot;&gt;一、「切 workspace 沒辦法用 cmd+數字」&lt;/h3&gt;
&lt;p&gt;這是被提最多次的一個。從 cmux 換過來的人習慣 &lt;code&gt;cmd+1/2/3&lt;/code&gt; 直接切，到了 herdr 翻半天找不到；也有人因此直接下結論，說優點沒高過轉換成本。&lt;/p&gt;
&lt;p&gt;我先去翻預設設定：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ herdr --default-config | grep -E &quot;switch_tab|switch_workspace|focus_agent&quot;
# focus_agent = &quot;&quot;        # optional indexed binding, e.g. &quot;prefix+alt+1..9&quot;
# switch_tab = &quot;prefix+1..9&quot;
# switch_workspace = &quot;&quot;   # optional indexed binding, e.g. &quot;prefix+shift+1..9&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;答案在這三行裡，而且分成兩件事：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;切 tab&lt;/strong&gt; 用 &lt;code&gt;prefix+1..9&lt;/code&gt;，&lt;strong&gt;本來就是預設&lt;/strong&gt;。有人測出來可以用但不確定是不是預設值，是的，就是。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;切 workspace&lt;/strong&gt;（大家口語講的「切 session」）&lt;strong&gt;預設是空的&lt;/strong&gt;。不是沒做，是沒綁。所以你翻遍快捷鍵表也找不到——它根本沒出現在表上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那綁上去會怎樣？我寫了一份 &lt;code&gt;~/.config/herdr/config.toml&lt;/code&gt; 實測：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[keys]
switch_workspace = [&quot;prefix+shift+1..9&quot;, &quot;ctrl+alt+1..9&quot;]
focus_agent      = [&quot;prefix+alt+1..9&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;陣列語法是重點：&lt;strong&gt;保留原本的 prefix 版本，同時加上一組免 prefix 的直接和弦&lt;/strong&gt;。存檔之後：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ herdr config check
config: ok

$ herdr server reload-config
{&quot;result&quot;:{&quot;diagnostics&quot;:[],&quot;status&quot;:&quot;applied&quot;,&quot;type&quot;:&quot;config_reload&quot;}}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不用重啟。我當時三隻 agent 正跑著，&lt;code&gt;status: applied&lt;/code&gt; 之後 &lt;code&gt;herdr agent list&lt;/code&gt; 三隻原封不動。這點我覺得比功能本身還值得記：&lt;strong&gt;改鍵位不用付「重來一次」的代價&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;然後我實際按了三次，記錄 focus 跑到哪個 workspace：&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;按下去&lt;/th&gt;&lt;th&gt;結果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ctrl+alt+1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切到 workspace #1 ✅&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;cmd+2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;完全沒反應 ❌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;ctrl+alt+2&lt;/code&gt;&lt;/td&gt;&lt;td&gt;切到 workspace #2 ✅&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;所以大家想要的功能一直都在，只是&lt;strong&gt;你想要的那組按鍵永遠不會成功&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&quot;二為什麼偏偏是-cmd-不行&quot;&gt;二、為什麼偏偏是 &lt;code&gt;cmd&lt;/code&gt; 不行&lt;/h3&gt;
&lt;p&gt;這點 herdr 文件講得比我清楚，而且我覺得寫得很誠實：一個和弦要活著抵達 herdr，得先穿過三層——作業系統、你的外層終端機、以及 pane 裡正在跑的程式。&lt;code&gt;cmd&lt;/code&gt; 系列在 macOS 幾乎都被前兩層吃光了，&lt;code&gt;alt&lt;/code&gt; 系列則會被 macOS 合成成特殊字元。&lt;/p&gt;
&lt;p&gt;文件裡提到他們把 Ghostty、iTerm2、Terminal.app、kitty、WezTerm、Alacritty、Warp、Windows Terminal、GNOME Terminal、Konsole 十種終端機的預設鍵位、加上 GNOME 與 KDE 的全域快捷鍵全部對照過一遍，結論是只有 &lt;code&gt;ctrl+alt&lt;/code&gt; 這一家幾乎沒人佔用。上面那張表就是這句話的實驗結果。&lt;/p&gt;
&lt;p&gt;另一種常見的回報是「有些 keymap 會無法生效」——而那個情境是&lt;strong&gt;在 tmux 裡面再開 herdr&lt;/strong&gt;。那就是第四層攔截，tmux 自己的 prefix 會先咬一口。這不是 herdr 壞掉，是和弦沒穿過去。&lt;/p&gt;
&lt;p&gt;（順帶一提，那個「在 tmux 裡面開 herdr」的用法我覺得滿聰明的：外層 tmux 切 Neovim 跟 herdr，內層 herdr 管 agent。兩個工具不用二選一。）&lt;/p&gt;
&lt;h3 id=&quot;三本質上還是-tui然後他們跑去-orca-了&quot;&gt;三、「本質上還是 TUI」——然後他們跑去 Orca 了&lt;/h3&gt;
&lt;p&gt;這是最大的分流，好幾個人都提到 Orca。講得最完整的一種說法是：用了兩天還是不習慣這個介面，號稱滑鼠可以用但骨子裡仍是 TUI，所以改用 Orca；並且補了一句我認為最準的判斷——&lt;strong&gt;herdr 適合原本就習慣 tmux 的人&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://superset.sh/compare/orca-alternative&quot;&gt;Orca&lt;/a&gt; 是桌面 Agent IDE，MIT、大約 39k stars，自帶終端機、檔案編輯器、git worktree 隔離，還有手機 app。跟 herdr 是完全不同種的東西。&lt;/p&gt;
&lt;p&gt;我覺得這個分流不是「誰比較好」，而是 herdr 自己 compare 頁那一列的實際演出：&lt;strong&gt;Runs inside your existing terminal&lt;/strong&gt;。你要的是「終端機什麼都不用變」，那是 herdr；你要的是「給我一個完整的 app」，那是 Orca。社群的人流方向，完全照這條線走。&lt;/p&gt;
&lt;p&gt;有趣的是問出那個問題的人自己結論相反：研究完 Orca 之後發現反而不是自己要的，笑說沒想到已經默默變成習慣 TUI 的人，到現在還在用 herdr。&lt;/p&gt;
&lt;h3 id=&quot;附帶一個到處在流傳的錯誤資訊&quot;&gt;附帶：一個到處在流傳的錯誤資訊&lt;/h3&gt;
&lt;p&gt;查 herdr 的中文資料時，我看到不只一處寫著「AGPL-3.0 雙授權，商業組織需要購買商業授權」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這是過期資訊。&lt;/strong&gt; 作者在 YC 公告裡明講換掉了，他的說法是希望每個人都能自由使用；我也直接查了 GitHub API 確認：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ curl -s https://api.github.com/repos/herdrdev/herdr | grep -A2 &apos;&quot;license&quot;&apos;
  &quot;license&quot;: { &quot;key&quot;: &quot;apache-2.0&quot;, &quot;spdx_id&quot;: &quot;Apache-2.0&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apache-2.0。如果你正要拿這個工具去說服公司，別引到那份舊資料。&lt;/p&gt;
&lt;h2 id=&quot;誰做的&quot;&gt;誰做的&lt;/h2&gt;
&lt;p&gt;Herdr 是 Can Celik 一個人用 Rust 寫的，&lt;a href=&quot;https://github.com/herdrdev/herdr&quot;&gt;GitHub repo&lt;/a&gt; 2026 年 3 月 27 日開張，到我寫這篇的今天（8 月 24 日）約三萬兩千顆星。五個月。&lt;/p&gt;
&lt;p&gt;8 月 6 日他發文宣布 &lt;a href=&quot;https://herdr.dev/blog/herdr-is-joining-y-combinator/&quot;&gt;加入 Y Combinator F26 batch&lt;/a&gt;，那篇文章我建議直接讀原文，因為起心動念的那段很好：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;四個月前我在找下一份工作，想著職涯要往哪走，想著軟體工程的未來、我的業餘專案、投出去的履歷、白板面試（我到現在還是不敢相信我們在做這種事）。然後某個瞬間我意識到：我就是那個瓶頸。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以及他對「每家公司都在出自己的 agent」這件事的態度：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;你知道現在每家公司都在發表自己的 agent 吧？不要這樣做。讓我的 agent 去整合你的產品，別再給我一個新的 agent。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Herdr 本身就是這句話的實作——它不做 agent，不包裝 agent，不取代 agent，它只是給你已經在用的那些 agent 一個住的地方。&lt;/p&gt;
&lt;p&gt;license 從 AGPL 換成了 Apache-2.0，他的說法是「我希望每個人都能自由地使用 Herdr」。目前版本 0.8.2。&lt;/p&gt;
&lt;h2 id=&quot;我的判斷誰該裝誰其實不用&quot;&gt;我的判斷：誰該裝，誰其實不用&lt;/h2&gt;
&lt;p&gt;先講不用的，有兩種。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一種，你一次只跑一隻 agent，而且都在自己筆電上開著螢幕盯&lt;/strong&gt;。多一層 runtime，你得到的主要是「關蓋不死」，而你本來就沒在關蓋。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二種比較關鍵：你其實想要的是一個 app，不是一個終端機&lt;/strong&gt;。前面那段社群回饋已經把這條線畫得很清楚了——覺得「滑鼠能點但骨子裡還是 TUI」很彆扭的人，最後都走去 Orca 了。這不是誰比較爛，是兩種人。你如果打開終端機會覺得鬆一口氣，herdr 是你的；你如果覺得終端機是不得已才開的東西，那你要的是 Orca 那類東西，而且你會比較快樂。&lt;/p&gt;
&lt;p&gt;值得裝的情況大概是這三種：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你固定同時跑兩隻以上的 agent，而且常常忘記某一隻在等你&lt;/li&gt;
&lt;li&gt;你有跑幾小時起跳的任務，不想被自己的作息綁住&lt;/li&gt;
&lt;li&gt;你有遠端機器（VPS、公司的開發機），想在筆電跟遠端之間無痛切換&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;還有一個我覺得要先講清楚的期待管理：&lt;strong&gt;Herdr 不是 agent orchestrator&lt;/strong&gt;。它不會幫你設計 multi-agent 分工、不會幫你拆任務、不會決定誰做什麼。它做的是更底層的兩件事——讓 agent 有地方住，讓你（跟其他 agent）看得到它們的狀態。編排的邏輯還是你自己或你的 agent 要寫。&lt;/p&gt;
&lt;p&gt;這個定位我覺得反而是它的優點。作者在 YC 那篇裡有句話講到我心坎裡：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在一個「多加一個功能幾乎沒有成本」的時代，決定什麼東西可以進核心，才是最重要的決定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這次實測我只跑在本機。真正想試但還沒試的是把長時間任務推到 VPS 上跑——目前我的做法是開著筆電硬撐，這顯然不是個做法。至於本機這段，「把終端機整個 quit 掉、agent 照跑」這件事我是真的驗過了，上面那段 &lt;code&gt;herdr status&lt;/code&gt; 就是當下的輸出。&lt;/p&gt;
&lt;p&gt;真的要一句話講完 Herdr 為什麼存在，我會說：agent 已經強到可以自己跑好幾個小時了，但我們給它的容身之處，還是那個「你一關蓋就消失」的終端機視窗。這中間的落差，總得有人去補嘛(笑 XD)。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;參考連結&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://herdr.dev/&quot;&gt;herdr.dev&lt;/a&gt; — 官網，首頁那個 hero 是可以點的互動 mock&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://herdr.dev/compare/&quot;&gt;herdr.dev/compare&lt;/a&gt; — 跟 tmux、zellij、cmux、Warp 等九個工具的對照表&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/herdrdev/herdr&quot;&gt;herdrdev/herdr&lt;/a&gt; — 原始碼（Rust，Apache-2.0）&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://herdr.dev/blog/herdr-is-joining-y-combinator/&quot;&gt;Herdr is joining Y Combinator&lt;/a&gt; — 作者 Can Celik 的公告全文&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.B5smFhCL.webp" medium="image"/><category>Herdr</category><category>AI Agent</category><category>Claude Code</category><category>Codex</category><category>終端機</category><category>tmux</category><category>開源</category><category>Rust</category><enclosure url="https://bobochen.dev/_astro/cover.B5smFhCL.webp" length="0" type="image/png"/></item><item><title>GOMAXPROCS 這個講了五年的坑，Go 1.25 已經修了——但只有 go.mod 寫對才生效</title><link>https://bobochen.dev/blog/private-cloud-05-gomaxprocs-cgroup-go125/</link><guid isPermaLink="true">https://bobochen.dev/blog/private-cloud-05-gomaxprocs-cgroup-go125/</guid><description>實測同一顆 toolchain、同一份原始碼、同一個 --cpus=1.5，只把 go.mod 的 go 指令從 1.25 改成 1.24，throttled_usec 就從 2.83 秒變成 52.44 秒、p99 從 39ms 變成 178ms。另外量到兩件沒什麼人寫的事：--cpus=1 給的 GOMAXPROCS 是 2 不是 1，以及 Go 1.25 會在跑的時候自己跟著 cgroup 改。</description><pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;我差點照抄了自己的筆記&quot;&gt;我差點照抄了自己的筆記&lt;/h2&gt;
&lt;p&gt;這篇的起點是一條我抄了很多年的筆記：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Go 在容器裡會把 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 設成宿主機的核心數，不是 cgroup 的 CPU limit。要嘛手動設，要嘛用 uber 的 &lt;code&gt;automaxprocs&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;它整理自 2019 到 2024 年之間的一堆文章，每一篇都這麼講，看起來天經地義。&lt;/p&gt;
&lt;p&gt;但下筆前我停了一下。Go 1.25 的 release note 說這件事修了——runtime 會自己去讀 cgroup 的 CPU 限制。如果是真的，這條筆記就是過期資訊；照抄，等於在 2026 年發表一個 2025 年 8 月就已經反過來的結論。&lt;/p&gt;
&lt;p&gt;所以我把 &lt;code&gt;docker run --cpus=1.5&lt;/code&gt; 真的打了下去，想花十分鐘驗證「修了」兩個字。&lt;/p&gt;
&lt;p&gt;「修了」是真的。但它跟我想像的完全不一樣：&lt;strong&gt;開關不在 toolchain，在 go.mod 的一行字&lt;/strong&gt;；它算出來的數字跟 &lt;code&gt;--cpus&lt;/code&gt; 給的對不上；而且就算全部設對，kernel 那層照樣凍你。&lt;/p&gt;
&lt;p&gt;十分鐘變成三個坑，外加一個把我自己打臉的 JVM 對照。&lt;/p&gt;
&lt;h2 id=&quot;這篇的-lab-環境一台-m2-上的-orbstackcgroup-v2&quot;&gt;這篇的 lab 環境：一台 M2 上的 OrbStack，cgroup v2&lt;/h2&gt;
&lt;p&gt;所有數字都出自這一台，沒有引用別人的 benchmark。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker info --format &apos;CPUs={{.NCPU}} Cgroup={{.CgroupVersion}}/{{.CgroupDriver}}&apos;
CPUs=8 Cgroup=2/cgroupfs

$ docker version --format &apos;{{.Server.Version}} / {{.Server.KernelVersion}}&apos;
29.4.0 / 7.0.11-orbstack-00360-gc9bc4d96ac70
&lt;/code&gt;&lt;/pre&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;元件&lt;/th&gt;&lt;th&gt;版本&lt;/th&gt;&lt;th&gt;相關日期&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;宿主&lt;/td&gt;&lt;td&gt;Apple M2（4 P-core + 4 E-core）、macOS 26.5.2&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;容器 runtime&lt;/td&gt;&lt;td&gt;OrbStack 2.2.1 / Docker Engine 29.4.0&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;建置用 image&lt;/td&gt;&lt;td&gt;&lt;code&gt;golang:1.25&lt;/code&gt; → &lt;strong&gt;Go 1.25.12&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Go 1.25 於 2025-08 釋出&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;執行用 image&lt;/td&gt;&lt;td&gt;&lt;code&gt;debian:bookworm-slim&lt;/code&gt;&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JVM 對照組&lt;/td&gt;&lt;td&gt;&lt;code&gt;eclipse-temurin:21&lt;/code&gt; → &lt;strong&gt;Java 21.0.11&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;本文實測日期：2026-08-07。&lt;/p&gt;
&lt;h2 id=&quot;先講結果同一份原始碼gomod-差一行throttle-差-18-倍&quot;&gt;先講結果：同一份原始碼，go.mod 差一行，throttle 差 18 倍&lt;/h2&gt;
&lt;p&gt;固定總工作量 1600 輪，均分給 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 條 goroutine，兩邊都跑在 &lt;code&gt;--cpus=1.5&lt;/code&gt; 裡。&lt;/p&gt;















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;go.mod 寫 &lt;code&gt;go 1.25&lt;/code&gt;&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;go.mod 寫 &lt;code&gt;go 1.24&lt;/code&gt;&lt;/th&gt;&lt;th align=&quot;right&quot;&gt;倍數&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;GOMAXPROCS(0)&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;wall time&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;5.758s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;8.254s&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;+43%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;p50&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;4.131ms&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;9.009ms&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2.2×&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;p99&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;39.463ms&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;177.594ms&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;4.5×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;nr_throttled&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;56&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;82&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1.5×&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;throttled_usec&lt;/code&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;2,831,315&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;52,440,761&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;&lt;strong&gt;18.5×&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;兩個 binary 是&lt;strong&gt;同一顆 Go 1.25.12 toolchain 編出來的&lt;/strong&gt;，原始碼一個字都沒改。&lt;/p&gt;
&lt;p&gt;差別只有 &lt;code&gt;go.mod&lt;/code&gt; 裡那一行 &lt;code&gt;go&lt;/code&gt; 指令。&lt;/p&gt;
&lt;p&gt;接下來照我踩坑的順序走：先找出這個開關到底在哪一層（坑一），再看它算出來的數字準不準（坑二），然後回答「全設對了為什麼還是被凍」（坑三）。後面是 Go 1.25 另一個沒什麼人量過的新行為，和一個原本要當反面教材、結果打臉我的 JVM 對照。&lt;/p&gt;
&lt;h2 id=&quot;坑一容器感知這件事go-綁在語言版本上不是綁在-toolchain-上&quot;&gt;坑一：容器感知這件事，Go 綁在語言版本上不是綁在 toolchain 上&lt;/h2&gt;
&lt;p&gt;Go 1.25 的 release note 講得很清楚：runtime 會去看 cgroup 的 CPU bandwidth limit，如果比邏輯 CPU 數少，&lt;code&gt;GOMAXPROCS&lt;/code&gt; 就取那個較低的值。&lt;/p&gt;
&lt;p&gt;我原本以為「裝了 Go 1.25 就有」。&lt;/p&gt;
&lt;p&gt;不是。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GODEBUG&lt;/code&gt; 的預設值是&lt;strong&gt;跟著 go.mod 的語言版本走的&lt;/strong&gt;。把 &lt;code&gt;go 1.25&lt;/code&gt; 改成 &lt;code&gt;go 1.24&lt;/code&gt;，同一顆 toolchain 編出來的 binary 就退回舊行為，因為 &lt;code&gt;containermaxprocs&lt;/code&gt; 的預設值來自語言版本而不是編譯器版本。&lt;/p&gt;
&lt;p&gt;實測三組，全部 &lt;code&gt;--cpus=1.5&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# A：go.mod 寫 go 1.25
runtime.NumCPU(): 8
GOMAXPROCS(0)   : 2
cpu.max         : 150000 100000

# B：go.mod 寫 go 1.24（同一顆 toolchain 編的）
runtime.NumCPU(): 8
GOMAXPROCS(0)   : 8

# C：go.mod 寫 go 1.25，但 GODEBUG=containermaxprocs=0
GODEBUG         : &quot;containermaxprocs=0&quot;
runtime.NumCPU(): 8
GOMAXPROCS(0)   : 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;B 跟 C 的行為完全一樣，這反過來證明 B 的成因就是 GODEBUG 預設值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這件事的殺傷力在於它很安靜。&lt;/strong&gt; 你升級了 Go、CI 綠燈、測試全過、&lt;code&gt;go version&lt;/code&gt; 印出 1.25，然後線上 p99 還是老樣子——因為那個 module 的 &lt;code&gt;go.mod&lt;/code&gt; 從三年前就沒動過。&lt;/p&gt;
&lt;p&gt;⚠️ 順帶一提，&lt;code&gt;runtime.NumCPU()&lt;/code&gt; &lt;strong&gt;在兩種情況下都回 8&lt;/strong&gt;。它報的是啟動當下這個 process 可用的邏輯 CPU 數，不受 CPU bandwidth limit（&lt;code&gt;cpu.max&lt;/code&gt;）影響——但 &lt;code&gt;--cpuset-cpus&lt;/code&gt; 這種改 affinity 的設定它是會跟著變的。所以任何用 &lt;code&gt;NumCPU()&lt;/code&gt; 去算 worker pool 大小的程式碼，Go 1.25 也救不了它。&lt;/p&gt;
&lt;h2 id=&quot;坑二--cpus1-給的-gomaxprocs-是-2不是-1&quot;&gt;坑二：&lt;code&gt;--cpus=1&lt;/code&gt; 給的 GOMAXPROCS 是 2，不是 1&lt;/h2&gt;
&lt;p&gt;開關找到了。下一個問題：它到底會把 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 設成幾？&lt;/p&gt;
&lt;p&gt;我本來以為公式就是 quota 除以 period。實測不是。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--cpus=0.25 → cpu.max: 25000 100000   → GOMAXPROCS = 2
--cpus=0.5  → cpu.max: 50000 100000   → GOMAXPROCS = 2
--cpus=1    → cpu.max: 100000 100000  → GOMAXPROCS = 2
--cpus=1.5  → cpu.max: 150000 100000  → GOMAXPROCS = 2
--cpus=2.7  → cpu.max: 270000 100000  → GOMAXPROCS = 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;公式是 &lt;code&gt;max(2, ceil(quota / period))&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;有一個&lt;strong&gt;下限 2&lt;/strong&gt;。所以在 &lt;code&gt;--cpus=1&lt;/code&gt; 甚至 &lt;code&gt;--cpus=0.25&lt;/code&gt; 的容器裡，Go 仍然會開兩條 P，刻意超賣。&lt;/p&gt;
&lt;p&gt;這跟 &lt;code&gt;uber-go/automaxprocs&lt;/code&gt; 的行為不一樣——那套是無條件向下取整，&lt;code&gt;--cpus=1.5&lt;/code&gt; 會給你 1。&lt;/p&gt;
&lt;p&gt;我原本猜下限 2 是為了避免單一 P 被某個長時間不讓出的 goroutine 卡死整個 runtime。release note 確實沒解釋，但官方理由寫在 proposal（&lt;a href=&quot;https://github.com/golang/go/issues/73193&quot;&gt;golang/go#73193&lt;/a&gt;）裡，方向接近、但更具體：&lt;strong&gt;GOMAXPROCS=1 會關掉 scheduler 的所有平行性&lt;/strong&gt;，GC worker 跟應用 goroutine 只能輪流跑，看起來就像 GC 週期性地「暫停」了你的程式（原文：GC workers temporarily “pausing” the application）。&lt;/p&gt;
&lt;p&gt;同一段還有一個判斷值得抄下來：quota ≤ 1 本身就被視為「這個 workload 是 bursty」的訊號——所以 runtime 選擇寧可刻意超賣，也不掉進 GOMAXPROCS=1 的陷阱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;實務上的意思是&lt;/strong&gt;：如果你原本靠 &lt;code&gt;automaxprocs&lt;/code&gt; 拿到 1，升上 Go 1.25 之後會變成 2。這是行為改變，不是 bug，但值得在升級 checklist 上寫一行。&lt;/p&gt;
&lt;h2 id=&quot;坑三gomaxprocs-對了還是會被-throttle&quot;&gt;坑三：GOMAXPROCS 對了，還是會被 throttle&lt;/h2&gt;
&lt;p&gt;到這裡我以為可以收工了——開關對了，數字也對了。&lt;/p&gt;
&lt;p&gt;然後我看到 &lt;code&gt;cpu.stat&lt;/code&gt;。這是我這次最意外的一格。&lt;/p&gt;
&lt;p&gt;A 組的 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 是 2、配額是 1.5 核，看起來很合理。結果 &lt;code&gt;nr_throttled&lt;/code&gt; 還是 56 次，&lt;code&gt;throttled_usec&lt;/code&gt; 2.83 秒。&lt;/p&gt;
&lt;p&gt;原因是 CFS 的配額是&lt;strong&gt;按 100ms 一個週期發的&lt;/strong&gt;，兩條 P 全速跑的話 75ms 就把 150ms 的額度用完，剩下 25ms 整個 cgroup 被凍結。&lt;/p&gt;
&lt;p&gt;順帶解釋一個看起來像造假的數字：B 組的 &lt;code&gt;throttled_usec&lt;/code&gt; 是 52.44 秒，比 8.254 秒的 wall time 還大。那不是筆誤——這個計數器是&lt;strong&gt;每顆 CPU 的 cfs_rq 各自累加&lt;/strong&gt;上去的，不是牆鐘時間。A 組 2 條 P × 每個週期凍 25ms × 56 個週期 ≈ 2.8 秒，B 組 8 條 P × 凍 81.25ms × 82 個週期 ≈ 53 秒，兩組都跟表格對得上。&lt;/p&gt;
&lt;p&gt;下面這張圖把兩個決策點畫在一起。上半是 Go runtime 那一層：&lt;code&gt;GOMAXPROCS&lt;/code&gt; 決定同時可以有幾條 P 在跑 goroutine，這個數字在 Go 1.25 之後由 &lt;code&gt;cpu.max&lt;/code&gt; 推導。下半是 kernel 那一層：不管 runtime 開了幾條 P，CFS 都按 &lt;code&gt;period&lt;/code&gt; 為單位發配額，用完就把&lt;strong&gt;整個 cgroup&lt;/strong&gt;凍結到下一個週期開始。兩層是獨立的，所以上面那層設對只是減少浪費，不是免疫。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/private-cloud-05-gomaxprocs-cgroup-go125/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;所以「GOMAXPROCS 設對」不等於「不會被 throttle」，它只是讓&lt;strong&gt;同時搶那份配額的 P 從 8 條變成 2 條&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;八條 P 搶 1.5 核的配額，每條平均只拿到 0.19 核，但每條都以為自己有一整顆——排程器把時間切得更碎，context switch 更多，配額也用得更快。&lt;/p&gt;
&lt;p&gt;換句話說，它只是把 throttle 從&lt;strong&gt;災難級&lt;/strong&gt;降到&lt;strong&gt;背景雜訊級&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;18.5 倍的差距就是這個意思。&lt;/p&gt;
&lt;h2 id=&quot;go-125-會在程式跑的時候自己跟著-cgroup-改&quot;&gt;Go 1.25 會在程式跑的時候自己跟著 cgroup 改&lt;/h2&gt;
&lt;p&gt;release note 裡還有第二句話：這個值不只啟動時算一次，程式跑著也會跟著 cgroup 更新。這個行為我找不到什麼人實際量過，所以自己量。&lt;/p&gt;
&lt;p&gt;我讓一支程式每秒印一次 &lt;code&gt;GOMAXPROCS&lt;/code&gt;，中途用 &lt;code&gt;docker update --cpus&lt;/code&gt; 改配額：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t=06s  GOMAXPROCS=2  cpu.max=150000 100000
t=07s  GOMAXPROCS=2  cpu.max=600000 100000   ← 配額變了，runtime 還沒跟上
t=08s  GOMAXPROCS=6  cpu.max=600000 100000   ← 追上了
...
t=15s  GOMAXPROCS=2  cpu.max=50000 100000    ← 砍到 0.5 核
...
t=23s  GOMAXPROCS=4  cpu.max=400000 100000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GOMAXPROCS&lt;/code&gt; 真的跟著動了，三次都對。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;追上的延遲在一個取樣點以內。&lt;/strong&gt; 我的取樣是每秒一次，所以我只能說「≤1 秒」，不能說出更精確的數字——這是我這個實驗的解析度上限，不是 runtime 的實際延遲。&lt;/p&gt;
&lt;p&gt;要關掉這個行為是 &lt;code&gt;GODEBUG=updatemaxprocs=0&lt;/code&gt;，跟前面那個 &lt;code&gt;containermaxprocs&lt;/code&gt; 是兩個獨立開關。&lt;/p&gt;
&lt;p&gt;在 Kubernetes 上這件事有實際意義：&lt;strong&gt;VPA 改了 CPU limit 之後，Go 1.25 的服務不用重啟就會跟上。&lt;/strong&gt; 這在 in-place pod resize 逐漸可用的環境裡是一個真的差別。&lt;/p&gt;
&lt;h2 id=&quot;jvm-那半查詢值會跟著變但已經建好的執行緒池不會&quot;&gt;JVM 那半：查詢值會跟著變，但已經建好的執行緒池不會&lt;/h2&gt;
&lt;p&gt;我原本要拿 JVM 當「反面教材」，結果實測打臉了我自己。&lt;/p&gt;
&lt;p&gt;先講靜態值，&lt;code&gt;--cpus=1.5&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- JVM ---
java.version          : 21.0.11
availableProcessors() : 2
FJP commonPool par.   : 1
--- GC / JIT thread ---
 bool UseContainerSupport = true {product} {default}
 uint ParallelGCThreads   = 2 {product} {default}
 uint ConcGCThreads       = 1 {product} {ergonomic}
 intx CICompilerCount     = 2 {product} {ergonomic}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;UseContainerSupport&lt;/code&gt; 預設就是開的，&lt;code&gt;availableProcessors()&lt;/code&gt; 回 2 而不是 8。&lt;strong&gt;JVM 在這件事上比 Go 早了很多年&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;然後是動態測試。同樣每秒印一次，中途改配額：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t=08s  availableProcessors=2  FJP=1
t=09s  availableProcessors=6  FJP=1     ← 改成 --cpus=6，查詢值跟上了
t=18s  availableProcessors=6  FJP=1
t=19s  availableProcessors=1  FJP=1     ← 改成 --cpus=0.5，又跟上了
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;availableProcessors()&lt;/code&gt; 是會動的。&lt;/strong&gt; 我原本以為它啟動就定型，這個假設是錯的。&lt;/p&gt;
&lt;p&gt;真正凍住的是下面那一欄。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ForkJoinPool.commonPool()&lt;/code&gt; 的 parallelism 從頭到尾都是 &lt;strong&gt;1&lt;/strong&gt;——它在 JVM 啟動的那一刻用 &lt;code&gt;availableProcessors - 1&lt;/code&gt; 算完，之後不管配額怎麼變都不再重算。&lt;code&gt;ConcGCThreads&lt;/code&gt; 與 &lt;code&gt;CICompilerCount&lt;/code&gt; 也是同樣的道理，&lt;code&gt;{ergonomic}&lt;/code&gt; 這個標記的意思就是「啟動時由 JVM 自己決定」。&lt;/p&gt;
&lt;p&gt;所以兩邊的分歧不在「知不知道」，在「知道之後改不改」：&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Go 1.25&lt;/th&gt;&lt;th&gt;JVM 21&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;讀到的核心數會跟著 cgroup 變&lt;/td&gt;&lt;td&gt;✅ &lt;code&gt;GOMAXPROCS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ &lt;code&gt;availableProcessors()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;--cpus=0.5&lt;/code&gt; 給幾&lt;/td&gt;&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;（有下限 2）&lt;/td&gt;&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;（單純 ceil）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;已建好的執行緒池跟著變&lt;/td&gt;&lt;td&gt;✅ P 的數量就是 &lt;code&gt;GOMAXPROCS&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ FJP commonPool 凍在啟動值&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GC / JIT thread&lt;/td&gt;&lt;td&gt;不適用&lt;/td&gt;&lt;td&gt;❌ 啟動時 ergonomic 定型&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Go 之所以能跟著變，是因為 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 本身就是 scheduler 的參數；JVM 的執行緒池是應用層物件，改了查詢值不會回頭去重建它們。&lt;/p&gt;
&lt;h2 id=&quot;3-分鐘快速上手怎麼確認你的服務踩到了沒有&quot;&gt;3 分鐘快速上手：怎麼確認你的服務踩到了沒有&lt;/h2&gt;
&lt;p&gt;不用寫 benchmark，三行就知道。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. 進到你的容器裡，看 runtime 認為自己有幾核
kubectl exec -it &amp;#x3C;pod&gt; -- sh -c &apos;cat /sys/fs/cgroup/cpu.max&apos;
# 輸出 &quot;150000 100000&quot; 代表配額 1.5 核

# 2. 看有沒有在被 throttle（跑一陣子之後再看一次）
kubectl exec -it &amp;#x3C;pod&gt; -- sh -c &apos;grep -E &quot;nr_throttled|throttled_usec&quot; /sys/fs/cgroup/cpu.stat&apos;

# 3. 看你的 module 綁在哪個語言版本
head -3 go.mod
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;最小可用流程&lt;/strong&gt;：&lt;code&gt;cpu.stat&lt;/code&gt; 的 &lt;code&gt;throttled_usec&lt;/code&gt; 持續上升 → 看 &lt;code&gt;go.mod&lt;/code&gt; 的 &lt;code&gt;go&lt;/code&gt; 指令是不是 &amp;#x3C; 1.25 → 是的話把它提上去，重新編譯，再看一次同一個計數器。&lt;/p&gt;
&lt;p&gt;三個要記的旋鈕：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GODEBUG=containermaxprocs=0&lt;/code&gt; — 關掉容器感知，退回讀宿主機核心數&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GODEBUG=updatemaxprocs=0&lt;/code&gt; — 關掉執行期動態更新，只在啟動時算一次&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GOMAXPROCS=N&lt;/code&gt; 環境變數 — 明確指定，優先於上面兩者&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你的 module 因為別的原因不能提語言版本，&lt;code&gt;uber-go/automaxprocs&lt;/code&gt; 仍然可用。⚠️ 但要注意它跟 Go 1.25 的取整規則不同（無下限 2），而且兩個同時生效時是後設定的贏。還有一個只有在 &lt;code&gt;go.mod&lt;/code&gt; 已經 ≥ 1.25、卻仍想用 automaxprocs 取整規則時才要在意的細節：它是在 init 裡呼叫 &lt;code&gt;runtime.GOMAXPROCS()&lt;/code&gt;，而這個呼叫會一併關掉前面那節講的執行期自動更新（要救回來得自己呼叫 &lt;code&gt;runtime.SetDefaultGOMAXPROCS()&lt;/code&gt;）。語言版本 &amp;#x3C; 1.25 的話，&lt;code&gt;updatemaxprocs&lt;/code&gt; 本來就預設關著，沒有東西會被關掉，這段可以跳過。&lt;/p&gt;
&lt;p&gt;下面這張決策圖把上面幾條路串起來。左邊那條分岔是這篇的核心——&lt;strong&gt;語言版本才是開關，不是 toolchain 版本&lt;/strong&gt;。右邊兩條是你確定要自己控制時才走的路：明確設環境變數，或是在不能動 &lt;code&gt;go.mod&lt;/code&gt; 的情況下退回 &lt;code&gt;automaxprocs&lt;/code&gt;。中間那個「還在 throttle」的迴圈是坑三，它提醒你設對 &lt;code&gt;GOMAXPROCS&lt;/code&gt; 之後仍然要回去看 &lt;code&gt;cpu.stat&lt;/code&gt;，因為 CFS 那一層的行為沒有被改變。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/private-cloud-05-gomaxprocs-cgroup-go125/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;這件事在雲上是誰做的&quot;&gt;這件事在雲上是誰做的&lt;/h2&gt;
&lt;p&gt;我在公有雲上寫了二十年後端，從來沒有量過 &lt;code&gt;cpu.stat&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;不是因為我不在乎延遲，是因為&lt;strong&gt;那個旋鈕不在我手上&lt;/strong&gt;。Cloud Run 給我的是「一個 instance 幾 vCPU」，App Engine 給我的是 instance class，我調的是那個數字，不是 CFS 的 quota 與 period。托管平台把 cgroup 這一層整個收走了。&lt;/p&gt;
&lt;p&gt;代價是我也失去了診斷能力。服務 p99 有週期性尖峰的時候，我能看的只有 APM 的火焰圖，看不到「這一秒 kernel 把整個 cgroup 凍結了 25 毫秒」。&lt;/p&gt;
&lt;p&gt;自建平台上這一層是打開的。打開的意思是兩件事同時成立：&lt;strong&gt;你查得到根因，而且這個根因現在歸你負責。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;這篇我沒跑到的部分&quot;&gt;這篇我沒跑到的部分&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;P99 的絕對值不能當生產數字。&lt;/strong&gt; M2 是 4 P-core + 4 E-core 的異質架構，中間還隔了 Virtualization.framework 的排程。同樣的 CFS 配額落在 P-core 跟 E-core 上算力差很多。可以用的是「有沒有週期性尖峰」與「兩組的比例關係」，不能用毫秒數去對照 x86 server。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「宿主機 8 核」是 OrbStack Linux VM 的 8 個 vCPU&lt;/strong&gt;，不是 macOS 直接看到的 8 核。這裡數字剛好相同，容易誤讀。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;動態更新的延遲我只量到「≤1 秒」&lt;/strong&gt;，因為取樣就是每秒一次。要量出實際延遲需要改用高頻取樣或 runtime trace，我沒做。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;沒測 Kubernetes 上的 in-place resize&lt;/strong&gt;。我用的是 &lt;code&gt;docker update&lt;/code&gt;，跟 kubelet 改 pod 資源的路徑不同。VPA 那段是從機制推論的，不是實測。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;沒測多容器互相干擾&lt;/strong&gt;。真實 node 上還有其他 pod 在搶同一顆 CPU，&lt;code&gt;throttled_usec&lt;/code&gt; 的形狀會更亂。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;反思&quot;&gt;反思&lt;/h2&gt;
&lt;h3 id=&quot;技術面&quot;&gt;技術面&lt;/h3&gt;
&lt;p&gt;這題的教訓不是「Go 1.25 修好了」，是&lt;strong&gt;預設值會版本化&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GODEBUG&lt;/code&gt; 綁語言版本這個設計很合理——它保證舊 module 升 toolchain 不會行為突變。但它也意味著「我升級了」跟「我拿到新行為了」是兩件事，中間隔著一行你可能三年沒看過的 &lt;code&gt;go.mod&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我以後看任何「某版本修好了」的 release note，都會多問一句：這個修正是綁編譯器，還是綁語言版本。&lt;/p&gt;
&lt;h3 id=&quot;心態面&quot;&gt;心態面&lt;/h3&gt;
&lt;p&gt;開頭那份筆記不是隨手抄的——我當時是真心相信的。而且如果這次沒有動手，我到今天都還會信。&lt;/p&gt;
&lt;p&gt;更糟的是同一個實驗裡被打臉了兩次。第一次是筆記那條；第二次是 JVM——我原本要拿它當反面教材，寫「JVM 啟動就定型」，結果實測 &lt;code&gt;availableProcessors()&lt;/code&gt; 明明會動。&lt;strong&gt;兩個假設，兩次被自己的 lab 打臉。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;查資料查到的東西，跟自己跑出來的東西，是兩種不同強度的知識。我以前分不太清楚。&lt;/p&gt;
&lt;h3 id=&quot;有趣發現&quot;&gt;有趣發現&lt;/h3&gt;
&lt;p&gt;最反直覺的是那個下限 2。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--cpus=0.25&lt;/code&gt; 的容器，Go 給你兩條 P。也就是說在資源最緊的那一格，runtime 選擇的是&lt;strong&gt;刻意超賣&lt;/strong&gt;而不是老實對齊。這跟整個 cgroup 感知的立意看起來是矛盾的，但如果你想過 GOMAXPROCS=1 的世界長什麼樣——GC worker 一動，應用就得整個停下來等——它又非常合理。&lt;/p&gt;
&lt;p&gt;那些「明明可以更精確卻選擇不精確」的地方，通常藏著設計者真正在怕的東西。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.Cj-qJSaj.webp" medium="image"/><category>Go</category><category>Kubernetes</category><category>容器</category><category>cgroup</category><category>效能</category><category>JVM</category><enclosure url="https://bobochen.dev/_astro/cover.Cj-qJSaj.webp" length="0" type="image/png"/></item><item><title>把 DMARC 從 p=none 安全推到 p=reject 的實戰 playbook</title><link>https://bobochen.dev/blog/dmarc-none-to-reject-playbook/</link><guid isPermaLink="true">https://bobochen.dev/blog/dmarc-none-to-reject-playbook/</guid><description>p=none 沒有保護力,但直接跳 p=reject 會擋掉自家信。這篇是分階段、零停機把 DMARC 推到強制的實戰流程:寄件來源盤點、alignment 對齊的姓氏規則、rua 監控,以及 pct 被移除後現在該怎麼漸進。</description><pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;code&gt;p=none&lt;/code&gt; 的 DMARC 是很多公司的現狀:裝了,但等於沒鎖門——它只觀察、不擋任何冒名信。&lt;/p&gt;
&lt;p&gt;但你不能直接把它改成 &lt;code&gt;p=reject&lt;/code&gt;。因為一旦強制,&lt;strong&gt;所有「驗證沒對齊」的信都會被退回——包括你自己沒設好的那些合法寄件管道&lt;/strong&gt;。員工信、電子報、客服回覆、系統通知……任何一個沒對齊的,使用者就收不到。&lt;/p&gt;
&lt;p&gt;這篇是把 DMARC 從 &lt;code&gt;p=none&lt;/code&gt; 安全推到 &lt;code&gt;p=reject&lt;/code&gt; 的分階段 playbook。核心觀念是:&lt;strong&gt;前置步驟全部零風險,真正的風險集中在最後一步,而它被監控期擋著。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 這是「誰在冒用你的 Domain 寄信」系列的進階篇。不熟 SPF/DKIM/DMARC 先看 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 白話入門&lt;/a&gt;;想看這套流程的真實案例,看系列起點 &lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;我如何重現一個資安評等的 F&lt;/a&gt;;這套 playbook 真的按下去那天的記錄在 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4 執行日的假訊號&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;整條路長這樣——前面四格都不會擋掉任何一封信,只有最後一格會:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;那個回頭的箭頭是整套流程的重點:報告一冒出沒見過的寄件者,就退回階段 1 重新盤點,而不是照日曆往下走。&lt;/p&gt;
&lt;h2 id=&quot;先理解為什麼會擋到自家信alignment&quot;&gt;先理解:為什麼會擋到自家信(alignment)&lt;/h2&gt;
&lt;p&gt;DMARC 不只看 SPF/DKIM 有沒有過,還看「&lt;strong&gt;通過的那個網域&lt;/strong&gt;」和「&lt;strong&gt;From 顯示的網域&lt;/strong&gt;」對不對得起來,這叫 &lt;strong&gt;alignment(對齊)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;用系列裡的掛號信比喻來說,對齊就是一條&lt;strong&gt;姓氏規則&lt;/strong&gt;。信封上寫的寄件人是「陳家公司」,可是火漆印上蓋的卻是「陳家公司・第三分部」——因為交易信外包給自己的子網域 &lt;code&gt;mg.yourdomain.com&lt;/code&gt; 在寄。這兩個名字算不算同一家?看你設的是哪一種規則:&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;對齊就是一條姓氏規則:預設只看姓,嚴格則要求一字不差。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;寬鬆 relaxed(&lt;code&gt;adkim=r; aspf=r&lt;/code&gt;,預設)&lt;/strong&gt;:只看姓。&lt;code&gt;mg.yourdomain.com&lt;/code&gt; 和 &lt;code&gt;yourdomain.com&lt;/code&gt; 同姓,算同一家 → 對齊。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;嚴格 strict(&lt;code&gt;adkim=s; aspf=s&lt;/code&gt;)&lt;/strong&gt;:要一模一樣。差一個 &lt;code&gt;mg.&lt;/code&gt; 就不算 → 不對齊。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;幾乎所有公司的信,都是靠「只看姓」這條預設規則活著的。&lt;/strong&gt; 用子網域幫你寄信的 SaaS(交易信、電子報、客服)一抓一大把,規則一改嚴格,它們全變成外人。&lt;/p&gt;
&lt;p&gt;上面這張圖只畫了盤點時真的會用到的那一半。完整的比喻、以及「改成嚴格為什麼代價遠大於收益」的完整推導,在 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 的〈對齊(alignment):姓氏規則〉&lt;/a&gt;;這裡只留執行時要記住的兩句:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DMARC 是 OR 邏輯&lt;/strong&gt;:SPF 對齊且通過,&lt;strong&gt;或&lt;/strong&gt; DKIM 對齊且通過,任一成立就算 pass。所以「改 strict 一定爆炸」是過度斷言——真正會整批掛掉的,是 DKIM 的 &lt;code&gt;d=&lt;/code&gt; 和 Return-Path 都掛在子網域上、而你又把兩個 tag 一起設成 &lt;code&gt;s&lt;/code&gt; 的組合(真的長成這樣的一條管道在 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4&lt;/a&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;但改成嚴格,擋不掉一般的冒名信。&lt;/strong&gt; 偽造信從來就不是任何一個「陳家」簽的,在寬鬆規則下本來就已經失敗;你多擋掉的幾乎都是自己的電子報、帳單和密碼重設信。(唯一的例外是子網域被接管的情況,見 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2&lt;/a&gt;。)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以這套流程的預設就是:&lt;code&gt;adkim=r; aspf=r&lt;/code&gt; 放著別動。&lt;/p&gt;
&lt;h2 id=&quot;階段-0先能看見設-rua&quot;&gt;階段 0:先能「看見」(設 rua)&lt;/h2&gt;
&lt;p&gt;沒有報告,你不知道誰會被擋。第一步把 DMARC 加上 &lt;code&gt;rua=mailto:...&lt;/code&gt;,&lt;strong&gt;維持 &lt;code&gt;p=none&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原始 XML 人是看不了的,丟給 DMARC 分析服務解析(&lt;a href=&quot;https://dmarcian.com&quot;&gt;dmarcian&lt;/a&gt;、Postmark DMARC、Valimail 等;或自架開源解析器)。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這步零風險、零投遞影響,卻是整個流程的基礎。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;階段-1盤點所有合法寄件來源最關鍵&quot;&gt;階段 1:盤點所有合法寄件來源(最關鍵)&lt;/h2&gt;
&lt;p&gt;列出每一個會用 &lt;code&gt;@yourdomain.com&lt;/code&gt; 寄信的東西,常見的有:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;公司信箱(Google Workspace / Microsoft 365)&lt;/li&gt;
&lt;li&gt;交易信 / 系統通知(Mailgun、SendGrid、SES、Postmark……通常走子網域如 &lt;code&gt;mg.&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;行銷電子報(Mailchimp、HubSpot……)&lt;/li&gt;
&lt;li&gt;客服系統(Zendesk、Intercom……)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最容易遺漏的&lt;/strong&gt;:電子簽核、問卷、CRM、招募系統、發票系統&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但只列出名字不算盤點完。清單上的&lt;strong&gt;每一條&lt;/strong&gt;,都要走過同樣三個問題:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;三個問題全部答完,這條管道才算盤好。&lt;strong&gt;遺漏一條,它的信就會在 reject 時死掉。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;真實案例:&lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;系列起點那篇&lt;/a&gt;裡,我盤點後發現高流量的交易信和電子報其實早就對齊了,缺口主要落在員工信和客服——爆炸半徑比想像中小。但盤點清單不會就此封閉:真正推上去那天,還是有一條電商管道的 DKIM 沒 provision 好(&lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4&lt;/a&gt;)。盤點讓你知道風險多大,監控期才是抓漏的那一關。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;階段-2鋪設零風險&quot;&gt;階段 2:鋪設(零風險)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SPF&lt;/strong&gt;:把所有合法來源補進去,結尾先用 &lt;code&gt;~all&lt;/code&gt;(softfail,不會擋信)。注意 10-lookup 上限。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DKIM&lt;/strong&gt;:每個寄件管道都開簽章(Google Workspace 後台、各 SaaS 的 DKIM 設定)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DMARC&lt;/strong&gt;:維持 &lt;code&gt;p=none&lt;/code&gt;,但 rua 已經在收報告。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些動作都是「多授權 / 加簽章」,&lt;strong&gt;只增不減&lt;/strong&gt;,不會擋掉任何現有信件。&lt;/p&gt;
&lt;h2 id=&quot;階段-3監控24-週&quot;&gt;階段 3:監控(2–4 週)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;讀 rua 報告,確認&lt;strong&gt;每一個合法來源都 pass 且對齊&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;揪出階段 1 沒想到的寄件者(它們會在報告裡冒出來),補上授權。&lt;/li&gt;
&lt;li&gt;這步是整個流程的&lt;strong&gt;安全閥&lt;/strong&gt;——不要急,讓報告告訴你還有誰沒對齊。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;階段-4漸進強制&quot;&gt;階段 4:漸進強制&lt;/h2&gt;
&lt;p&gt;到這裡才第一次真的會擋信,所以一階一階來:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;p=quarantine&lt;/code&gt;——驗不過的信進垃圾桶,還撈得回來。放個一到兩週,持續盯 rua。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p=reject&lt;/code&gt;——驗不過的信直接被退。&lt;/li&gt;
&lt;li&gt;最後把 SPF 從 &lt;code&gt;~all&lt;/code&gt; 收緊成 &lt;code&gt;-all&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;每一階之間都要回頭看報告有沒有誤殺合法信;有,就退回上一階,補完再上。&lt;/p&gt;
&lt;h3 id=&quot;關於-pct那是舊-rfc-的做法&quot;&gt;關於 &lt;code&gt;pct&lt;/code&gt;:那是舊 RFC 的做法&lt;/h3&gt;
&lt;p&gt;早期的 playbook(包括我自己最早寫的版本)會教你用 &lt;code&gt;pct=25&lt;/code&gt; → &lt;code&gt;pct=50&lt;/code&gt; → &lt;code&gt;pct=100&lt;/code&gt;,讓政策只對一部分驗不過的信生效、再慢慢放大。&lt;strong&gt;這個標籤已經不在標準裡了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;DMARC 現在的規範是 2026 年 5 月發布的 &lt;strong&gt;&lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9989&quot;&gt;RFC 9989&lt;/a&gt;&lt;/strong&gt;,它取代了原本的 RFC 7489 與 RFC 9091,並把 DMARC 從 Informational 升級成 Standards Track。&lt;code&gt;pct&lt;/code&gt; 在附錄 A.6 被列為移除項目,registry 也已標成 historic。&lt;/p&gt;
&lt;p&gt;實務上的意思是:某些收件方的舊實作可能還讀得懂 &lt;code&gt;pct&lt;/code&gt;,但&lt;strong&gt;你不能把上線計畫建立在「有些人還吃這個標籤」上面&lt;/strong&gt;——一部分人吃、一部分人不吃,你只會得到一個連自己都說不準生效範圍的政策。&lt;/p&gt;
&lt;p&gt;那現在怎麼漸進?&lt;strong&gt;用政策階梯本身當節奏,而不是百分比&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;none&lt;/code&gt; → &lt;code&gt;quarantine&lt;/code&gt; → &lt;code&gt;reject&lt;/code&gt; 這三階,本來就是三個不同強度,一階一階爬。&lt;/li&gt;
&lt;li&gt;每階停留多久由 rua 報告決定,不是由日曆決定——報告連續乾淨才往上加一階。&lt;/li&gt;
&lt;li&gt;想更保守,可以讓子網域先進強制:主網域維持 &lt;code&gt;p=none&lt;/code&gt;,先設 &lt;code&gt;sp=quarantine&lt;/code&gt; 只對子網域生效,觀察沒事再動主網域。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;要注意的-side-effect&quot;&gt;要注意的 side-effect&lt;/h2&gt;
&lt;h3 id=&quot;轉寄信與郵件群組最常見的真實災情&quot;&gt;轉寄信與郵件群組(最常見的真實災情)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;p=reject&lt;/code&gt; 之下最容易被退的,往往不是冒名信,而是&lt;strong&gt;被轉寄過的自家信&lt;/strong&gt;。原因在 SPF 和 DKIM 的本質差別:SPF 只看「站在門口把信交出來的是誰」,信被轉寄一次,交信的就換成轉寄主機,SPF 當場斷掉;DKIM 的火漆印是壓在信上、跟著信走的,中間換幾手都還在。這條機制的完整對照圖在 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 的〈信被轉寄一次〉&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;對這份 playbook 來說,結論只有一句:&lt;strong&gt;把每一條管道的 DKIM 都簽好、簽對齊&lt;/strong&gt;,轉寄斷掉的 SPF 就不會要你的命(OR 邏輯,DKIM 那邊過就算過)。大型收件方(Gmail、Microsoft 365)另外有 ARC 可以緩解,但那是對方的善意,不是你能控制的。至於會改標題、加尾註的郵件論壇,它連 DKIM 都會弄壞——那種只能個案處理。&lt;/p&gt;
&lt;h3 id=&quot;其他幾個&quot;&gt;其他幾個&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;遺漏的寄件者&lt;/strong&gt;:最常見的翻車原因——監控期就是為了抓它們。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;relaxed 對齊別動&lt;/strong&gt;:&lt;code&gt;adkim=r; aspf=r&lt;/code&gt; 保留。改嚴格擋不掉一般的冒名信,卻會擋掉自己人(上面講過)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SPF 10-lookup&lt;/strong&gt;:include 太多會 &lt;code&gt;permerror&lt;/code&gt; 讓 SPF 整個失效;用 SPF flattening 或精簡 include。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sp=&lt;/code&gt; / &lt;code&gt;np=&lt;/code&gt; 沒寫不代表沒設&lt;/strong&gt;:&lt;code&gt;sp=&lt;/code&gt; 缺席時,子網域直接繼承 &lt;code&gt;p=&lt;/code&gt;;&lt;code&gt;np=&lt;/code&gt;(不存在的子網域)缺席時繼承 &lt;code&gt;sp=&lt;/code&gt;。所以在 &lt;code&gt;p=reject&lt;/code&gt; 後面補一句 &lt;code&gt;sp=reject; np=reject&lt;/code&gt; &lt;strong&gt;不會改變任何一封信的處置&lt;/strong&gt;,只會讓彙整報告的 &lt;code&gt;policy_published&lt;/code&gt; 長得比較清楚。要補可以,但那一行打錯一個字,整筆記錄可能被降級成 &lt;code&gt;p=none&lt;/code&gt;——辛苦推上來的強制瞬間歸零。純粹寫爽的改動,反而是風險。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;一張表收尾&quot;&gt;一張表收尾&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;階段&lt;/th&gt;&lt;th&gt;動作&lt;/th&gt;&lt;th&gt;風險&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;DMARC 加 rua&lt;/td&gt;&lt;td&gt;零&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;盤點寄件來源&lt;/td&gt;&lt;td&gt;零&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;補 SPF/DKIM、&lt;code&gt;~all&lt;/code&gt;&lt;/td&gt;&lt;td&gt;零&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;監控報告 2–4 週&lt;/td&gt;&lt;td&gt;零&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;&lt;code&gt;quarantine&lt;/code&gt;→&lt;code&gt;reject&lt;/code&gt;、&lt;code&gt;-all&lt;/code&gt;&lt;/td&gt;&lt;td&gt;有(被前面擋著)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;把風險留到最後、用監控期當安全閥——這就是為什麼一個 email 認證的「F」聽起來嚇人,實際動工卻可以很穩。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;想看這套流程是怎麼從一個 F 開始的,回 &lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;系列起點:我如何重現一個資安評等的 F&lt;/a&gt;;想看它真的按下去那天發生什麼事、哪些嚇人的訊號其實是假警報,看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4 執行日的假訊號&lt;/a&gt;。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/images/og/dmarc-none-to-reject-playbook.png" medium="image"/><category>資安</category><category>email-security</category><category>DMARC</category><category>SPF</category><category>DKIM</category><enclosure url="https://bobochen.dev/images/og/dmarc-none-to-reject-playbook.png" length="0" type="image/png"/></item><item><title>SPF / DKIM / DMARC 到底在幹嘛?三個 DNS 記錄一次搞懂</title><link>https://bobochen.dev/blog/spf-dkim-dmarc-explained/</link><guid isPermaLink="true">https://bobochen.dev/blog/spf-dkim-dmarc-explained/</guid><description>email 的寄件認證三件套,用寄實體掛號信的比喻加圖解一次講清楚:SPF 是郵差白名單、DKIM 是跟著信走的火漆印、DMARC 是驗不過時的處置政策,以及最難懂的「對齊」——姓氏規則。附 dig 自我檢查指令。</description><pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;你有沒有想過:為什麼詐騙集團能寄出一封「看起來真的來自某銀行」的信?&lt;/p&gt;
&lt;p&gt;因為 email 有個先天缺陷——&lt;strong&gt;寄件人地址(From)可以隨便填&lt;/strong&gt;,就像實體信封上的寄件人欄,你想寫誰就寫誰。SMTP 協定誕生的年代沒人想到要防偽。&lt;/p&gt;
&lt;p&gt;於是後來補了三道關卡:&lt;strong&gt;SPF、DKIM、DMARC&lt;/strong&gt;。它們都是你網域 DNS 裡的幾筆記錄,合起來回答三個問題:&lt;strong&gt;誰能幫我寄信?信有沒有被動手腳?驗不過時收件方該怎麼辦?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這篇用「寄一封實體掛號信」的比喻,把這三個一次講清楚。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 這是「誰在冒用你的 Domain 寄信」系列的入門篇。系列起點是一篇真實案例:&lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;我如何重現一個資安評等的 F&lt;/a&gt;;讀完想動手把 DMARC 推到 &lt;code&gt;p=reject&lt;/code&gt;,接 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 實戰 playbook&lt;/a&gt;;想看真的按下去那天發生什麼事,看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4 執行日記錄&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;先看全貌一封信抵達的那幾秒鐘&quot;&gt;先看全貌:一封信抵達的那幾秒鐘&lt;/h2&gt;
&lt;p&gt;在拆解三個記錄之前,先看一眼它們合起來長什麼樣。一封信寄到收件伺服器,大約是這樣跑完的:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;三件事值得先記住:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SPF 和 DKIM 是兩條各自獨立的路&lt;/strong&gt;,誰過誰不過互不影響。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兩條路的終點都是「對齊」&lt;/strong&gt;——這是 DMARC 才有的關卡,也是全篇最難的一段,我後面用一整節講。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只要其中一條走到底,DMARC 就算過&lt;/strong&gt;(這是 OR 邏輯,不是 AND)。這個設計等一下講「轉寄」的時候會救你一命。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;spf誰可以幫我寄信郵差白名單&quot;&gt;SPF:誰可以幫我寄信(郵差白名單)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;比喻&lt;/strong&gt;:你公告「只有 A、B、C 這三家快遞,能用我公司的名義寄件」。郵局收到信,先看寄件方是不是名單上的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技術&lt;/strong&gt;:SPF 是一筆 DNS TXT,列出被授權的寄信伺服器。收件方檢查「這封信是不是從名單上的伺服器來的」。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;範例:&lt;code&gt;v=spf1 include:_spf.google.com ~all&lt;/code&gt;(用 Google Workspace 寄,其餘 softfail)。&lt;/p&gt;
&lt;p&gt;這裡先記一個細節,後面講轉寄會用到:&lt;strong&gt;SPF 看的是「站在門口把信交出來的那台機器」&lt;/strong&gt;,不是信本身。信的內容它一個字都不看。&lt;/p&gt;
&lt;p&gt;結尾那個 &lt;code&gt;all&lt;/code&gt; 機制最關鍵——它決定「名單以外的人來」時怎麼辦:&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;寫法&lt;/th&gt;&lt;th&gt;意思&lt;/th&gt;&lt;th&gt;防偽力&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;+all&lt;/code&gt;&lt;/td&gt;&lt;td&gt;放行任何人&lt;/td&gt;&lt;td&gt;等於沒設(危險)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;?all&lt;/code&gt;&lt;/td&gt;&lt;td&gt;neutral(中性)&lt;/td&gt;&lt;td&gt;幾乎沒用&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;~all&lt;/code&gt;&lt;/td&gt;&lt;td&gt;softfail(收下但標記)&lt;/td&gt;&lt;td&gt;中&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;-all&lt;/code&gt;&lt;/td&gt;&lt;td&gt;hardfail(拒絕)&lt;/td&gt;&lt;td&gt;強&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;⚠️ 陷阱:SPF 有「&lt;strong&gt;10 次 DNS 查詢&lt;/strong&gt;」上限,&lt;code&gt;include&lt;/code&gt; 串太多會讓整筆 SPF 失效。&lt;/p&gt;
&lt;h2 id=&quot;dkim這封信確實是我且沒被竄改火漆印&quot;&gt;DKIM:這封信確實是我、且沒被竄改(火漆印)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;比喻&lt;/strong&gt;:你在信封封口蓋一個只有你有的火漆印章;對方比對印模,就知道是不是你寄的、有沒有被中途拆過。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技術&lt;/strong&gt;:寄件伺服器用&lt;strong&gt;私鑰&lt;/strong&gt;對信件簽章,&lt;strong&gt;公鑰&lt;/strong&gt;放在 DNS(&lt;code&gt;selector._domainkey.你的網域&lt;/code&gt;)。收件方用公鑰驗章。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;它同時證明兩件事:信來自持有私鑰的人 + 內容沒被中途改過。&lt;/p&gt;
&lt;p&gt;範例:Google Workspace 的 selector 是 &lt;code&gt;google&lt;/code&gt;,所以公鑰在 &lt;code&gt;google._domainkey.你的網域&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id=&quot;信被轉寄一次spf-壞了dkim-沒事&quot;&gt;信被轉寄一次:SPF 壞了,DKIM 沒事&lt;/h2&gt;
&lt;p&gt;這是入門讀者最容易踩到、卻很少有人先講的一件事,而且它直接解釋了為什麼 DMARC 要設計成 OR 邏輯。&lt;/p&gt;
&lt;p&gt;SPF 檢查的是「&lt;strong&gt;站在門口把信交出來的是誰&lt;/strong&gt;」。信只要被轉寄一次——同事設了自動轉信、或信寄進一個郵件群組再發給所有成員——最後把信交到收件伺服器手上的,就是那台轉寄主機。它當然不在你的 SPF 名單上,SPF 就 fail 了。&lt;strong&gt;不是信被改壞,是交信的人換了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;DKIM 不一樣。火漆印是&lt;strong&gt;壓在信上、跟著信走的&lt;/strong&gt;。中間換手幾次都還在,只要沒人去動信的內容和被簽章的那些標頭,公鑰照樣驗得過。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這個差別在 &lt;code&gt;p=none&lt;/code&gt; 的時候完全看不出來(反正什麼都不會被擋)。但一旦走到 &lt;code&gt;p=reject&lt;/code&gt;,它就是最常見的真實災情:&lt;strong&gt;轉寄信和郵件群組被退回&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也因為這樣,DMARC 才規定「SPF 或 DKIM 任一邊對齊通過就算過」——&lt;strong&gt;DKIM 是信被轉寄之後,唯一還活著的那道&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;dmarc驗不過時怎麼辦--給我報告處置政策&quot;&gt;DMARC:驗不過時怎麼辦 + 給我報告(處置政策)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;比喻&lt;/strong&gt;:前兩關是「檢查」,DMARC 是「公司規定」——萬一掛號信驗不過,是退回、丟到一旁、還是照收?順便每天給你一份「今天有多少冒名信」的報表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技術&lt;/strong&gt;:DMARC 是 &lt;code&gt;_dmarc.你的網域&lt;/code&gt; 的 TXT,做三件事:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;政策 &lt;code&gt;p=&lt;/code&gt;&lt;/strong&gt;:&lt;code&gt;none&lt;/code&gt;(只觀察)、&lt;code&gt;quarantine&lt;/code&gt;(丟垃圾桶)、&lt;code&gt;reject&lt;/code&gt;(直接拒收)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;對齊(alignment)&lt;/strong&gt;:光 SPF/DKIM 通過還不夠,被驗過的那個網域要和 From 顯示的網域「對得起來」才算數。這是 DMARC 比單獨 SPF/DKIM 強的關鍵,下一節整節都在講它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;報告 &lt;code&gt;rua=&lt;/code&gt;&lt;/strong&gt;:把每日彙整報告寄到你指定的信箱。&lt;strong&gt;沒有 rua,你等於瞎子。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那三個 &lt;code&gt;p=&lt;/code&gt; 的差別,用門來想最快:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;範例:&lt;code&gt;v=DMARC1; p=none; rua=mailto:dmarc@你的網域; adkim=r; aspf=r&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;adkim=r; aspf=r&lt;/code&gt; 其實是&lt;strong&gt;預設值,不寫也一樣&lt;/strong&gt;。我還是把它寫出來,純粹是為了讓後面接手的人看到它、知道它是刻意留的——避免有人手癢改成 &lt;code&gt;s&lt;/code&gt;(下一節會講為什麼它的代價遠大於收益)。&lt;/p&gt;
&lt;p&gt;注意:&lt;code&gt;p=none&lt;/code&gt; 沒有保護力,只是觀察;真正擋冒名要走到 &lt;code&gt;p=quarantine&lt;/code&gt;/&lt;code&gt;reject&lt;/code&gt;——但中間有眉角,所以才有 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 playbook&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 查資料時的小提醒:DMARC 的規範在 2026-05 由 &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9989&quot;&gt;RFC 9989&lt;/a&gt; 取代了舊的 RFC 7489,正式升為 Standards Track。網路上大量文章還是照 RFC 7489 在寫,看到的時候記得它已經是舊版。對上線流程最直接的影響是 &lt;code&gt;pct&lt;/code&gt; 標籤被移除了,&lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3&lt;/a&gt; 有整理該怎麼改用政策階梯漸進。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;對齊alignment姓氏規則&quot;&gt;對齊(alignment):姓氏規則&lt;/h2&gt;
&lt;p&gt;這是整個系列最難懂的一段,值得慢慢講。&lt;/p&gt;
&lt;p&gt;問題出在:「&lt;strong&gt;信封上的名字&lt;/strong&gt;」和「&lt;strong&gt;火漆印上的名字&lt;/strong&gt;」不一定會一樣。&lt;/p&gt;
&lt;p&gt;因為現實裡,你的信常常是外包出去寄的——電子報交給 Mailchimp、交易信和系統通知交給 Mailgun。這類服務幫你寄的時候,很常是掛在你的&lt;strong&gt;子網域&lt;/strong&gt;底下(像 &lt;code&gt;mg.yourdomain.com&lt;/code&gt;),而不是主網域本身。&lt;/p&gt;
&lt;p&gt;於是變成這樣:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;信封上寫&lt;/strong&gt;「陳家公司」(收件人看到的 From 是 &lt;code&gt;@yourdomain.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;火漆印上寫&lt;/strong&gt;「陳家公司・第三分部」(DKIM 的 &lt;code&gt;d=mg.yourdomain.com&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DMARC 要判斷「這兩個到底算不算同一家」,有兩套規則:&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;對齊就是一條姓氏規則:預設只看姓,同姓就算同一家;改成嚴格,連分部都得一字不差。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;寬鬆 relaxed(&lt;code&gt;adkim=r&lt;/code&gt; / &lt;code&gt;aspf=r&lt;/code&gt;,預設)&lt;/strong&gt;:只看姓。同姓就算同一家,通過。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;嚴格 strict(&lt;code&gt;adkim=s&lt;/code&gt; / &lt;code&gt;aspf=s&lt;/code&gt;)&lt;/strong&gt;:要一模一樣,連分部都得對上,不通過。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這裡最該記住的一句:&lt;strong&gt;幾乎所有公司的信,都是靠「只看姓」這條預設規則活著的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;為什麼改成嚴格幾乎沒有收益代價卻很大&quot;&gt;為什麼「改成嚴格」幾乎沒有收益,代價卻很大&lt;/h3&gt;
&lt;p&gt;看到 &lt;code&gt;r&lt;/code&gt; 這個預設值,很多人的直覺反應是「這聽起來很鬆,收緊一點應該更安全」。&lt;/p&gt;
&lt;p&gt;不會。把它改成 &lt;code&gt;s&lt;/code&gt;,&lt;strong&gt;擋不掉一般的冒名信&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;偽造信從來就不是任何一個「陳家」簽的——它們用的是自己的網域、自己的鑰匙,連姓都不一樣。在寬鬆規則下,它們本來就已經在失敗了。收緊姓氏規則,對它們的處境沒有造成任何改變。&lt;/p&gt;
&lt;p&gt;你多擋掉的,幾乎都是&lt;strong&gt;自己的&lt;/strong&gt;電子報、帳單、密碼重設信——那些真的姓陳、只是名字後面掛了分部的信。&lt;/p&gt;
&lt;p&gt;唯一的例外寫在 &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc9989&quot;&gt;RFC 9989&lt;/a&gt; §11.8:如果你的某個子網域 DNS 委派給了別人、或哪天被接管,寬鬆規則會讓那個子網域簽出來的信也算「同姓」而通過。嚴格規則擋得掉這一種。但那本質上是子網域治理問題,而代價是自家所有掛子網域的寄件管道一起陣亡——先把子網域管好,通常划算得多。&lt;/p&gt;
&lt;p&gt;所以預設的 &lt;code&gt;r&lt;/code&gt; 不是「還沒調好」,是「應該留著」。&lt;/p&gt;
&lt;p&gt;(補一句給想追細節的人:因為 DMARC 是 OR 邏輯,只把其中一個 tag 改成 &lt;code&gt;s&lt;/code&gt; 未必當場出事——要 SPF 和 DKIM 兩邊都被收緊、而寄信管道剛好兩邊都掛在子網域上,才會整批掛掉。真的長成這樣的一條管道、以及在 &lt;code&gt;p=reject&lt;/code&gt; 上線那天為什麼決定不去動它,記在 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4&lt;/a&gt;。)&lt;/p&gt;
&lt;h2 id=&quot;三個一起看&quot;&gt;三個一起看&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SPF&lt;/strong&gt; 管「從哪台機器寄」、&lt;strong&gt;DKIM&lt;/strong&gt; 管「內容真偽」、&lt;strong&gt;DMARC&lt;/strong&gt; 管「驗不過怎麼辦 + 回報」。&lt;/li&gt;
&lt;li&gt;三者缺一不可:只有 SPF/DKIM 沒 DMARC,冒名信還是進得來;有 DMARC 但停在 &lt;code&gt;p=none&lt;/code&gt;,等於裝了監視器卻不鎖門。&lt;/li&gt;
&lt;li&gt;而&lt;strong&gt;對齊&lt;/strong&gt;是把前兩者和 From 綁在一起的那條線——沒有它,詐騙集團大可用自己的網域簽一封 DKIM 完全通過的信,然後在 From 寫你的名字。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;30-秒自我檢查&quot;&gt;30 秒自我檢查&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dig +short TXT yourdomain.com | grep spf1           # 有 SPF 嗎?結尾是 -all 還是 ?all?
dig +short TXT _dmarc.yourdomain.com                # 有 DMARC 嗎?p= 是什麼?
dig +short TXT google._domainkey.yourdomain.com     # (Google Workspace) DKIM 設了嗎?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;懶得記指令的話,&lt;a href=&quot;https://mxtoolbox.com&quot;&gt;mxtoolbox&lt;/a&gt;、&lt;a href=&quot;https://dmarcian.com&quot;&gt;dmarcian&lt;/a&gt;、&lt;a href=&quot;https://internet.nl&quot;&gt;internet.nl&lt;/a&gt; 都能一鍵體檢。&lt;/p&gt;
&lt;h2 id=&quot;為什麼要在意&quot;&gt;為什麼要在意&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;防冒名釣魚&lt;/strong&gt;:保護你的客戶、員工不被社交工程。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保護品牌&lt;/strong&gt;:沒人能假冒你寄垃圾信。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;送達率&lt;/strong&gt;:認證齊全的網域,信比較不會被丟進垃圾桶。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;這三個記錄是「設定」不是「開發」,改 DNS 就好,技術門檻其實很低——難的從來不是技術,是把它排進待辦。真的要動手把 DMARC 安全推到 &lt;code&gt;reject&lt;/code&gt;,接著看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 實戰 playbook&lt;/a&gt;;想先看看真的按下去那天會遇到什麼,看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4 執行日的假訊號&lt;/a&gt;。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/images/og/spf-dkim-dmarc-explained.png" medium="image"/><category>資安</category><category>email-security</category><category>SPF</category><category>DKIM</category><category>DMARC</category><enclosure url="https://bobochen.dev/images/og/spf-dkim-dmarc-explained.png" length="0" type="image/png"/></item><item><title>「大家真的都可以這麼早下班嗎」——不能，他們只是有後援</title><link>https://bobochen.dev/blog/no-backup-support-remote-work/</link><guid isPermaLink="true">https://bobochen.dev/blog/no-backup-support-remote-work/</guid><description>一則幼兒園接送的貼文十四萬人看過。留言區其實已經把答案講出來了：那些四點就出現在校門口的，多半不是爸媽。雙薪無後援的人只有四條路，我選了最貴的那條。</description><pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前幾天在 Threads 看到一則貼文：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;最近兒子上幼兒園，我和先生 17:30 下班，以最快的速度到學校接近 18:00，兒子居然已經是倒數三位走的。大家真的都可以這麼早下班嗎🥹&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;發文十一個小時，十四萬次瀏覽、九百多個讚、兩百多則留言。&lt;/p&gt;
&lt;p&gt;一句這麼日常的抱怨會炸開，通常不是因為它特別，而是因為它太普遍。&lt;/p&gt;
&lt;p&gt;我看到的第一個反應不是同情，是身體記憶。我知道那個畫面：教室的燈還亮著，椅子疊了一半，老師在收玩具，剩下的兩三個小孩坐在大廳，每聽到一次門口的聲音就抬一次頭。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;留言區早就把答案講完了只是沒人整理&quot;&gt;留言區早就把答案講完了，只是沒人整理&lt;/h2&gt;
&lt;p&gt;原 po 問的是：大家真的都可以這麼早下班嗎？&lt;/p&gt;
&lt;p&gt;留言的答案其實很一致。按讚最高的那則講得最直白：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這就是雙薪家庭，所以上一輩有本事催生的，請自己準備好當個合格的外援。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;其他幾則把同一件事講得更具體：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「通常都是有後援，我們之前都阿公阿嬤時間到會先去接。」&lt;/li&gt;
&lt;li&gt;「雙薪無後援真的很難，可以 18:00 就很棒了⋯⋯可以提前很多接的可能有後援。很多甚至 17:00 前接、連延托到 18:00 都沒有的，都是阿公阿嬤在接。」&lt;/li&gt;
&lt;li&gt;「無後援真的沒辦法準時 4 點去接。」&lt;/li&gt;
&lt;li&gt;「雙薪路過，我是找保母接喔，順便晚餐在保母家吃，我們下班再去接他回家。」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以答案是：&lt;strong&gt;不能&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那些四點就出現在校門口的人，多數不是下班比較早，而是&lt;strong&gt;出現的那個人根本不是爸媽&lt;/strong&gt;。是阿公阿嬤，是保母，是家裡有一方沒在上全職的班。&lt;/p&gt;
&lt;p&gt;「大家真的都可以這麼早下班嗎」這個問題問錯了。真正的變數不是下班時間，是&lt;strong&gt;你家有沒有第三個大人&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;這題的算術本來就兜不攏&quot;&gt;這題的算術本來就兜不攏&lt;/h2&gt;
&lt;p&gt;把時間攤開來看會更清楚：&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;時間&lt;/th&gt;&lt;th&gt;發生什麼事&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;16:00–16:30&lt;/td&gt;&lt;td&gt;幼兒園放學、開放接回&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;17:30&lt;/td&gt;&lt;td&gt;你下班&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;17:30–18:00&lt;/td&gt;&lt;td&gt;通勤&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;18:00&lt;/td&gt;&lt;td&gt;你抵達校門口，孩子是最後幾個&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;18:00–19:30&lt;/td&gt;&lt;td&gt;回家、煮飯、吃飯&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;原 po 自己在留言裡補了一句：「公司的彈性上下班只有 17:30–18:00，實在是不夠友善，回家煮飯吃飯常常都超過 19:30。」&lt;/p&gt;
&lt;p&gt;另一則留言則說：「學校 16:30 開放接回，常常 16:15 門口就滿滿家長。」&lt;/p&gt;
&lt;p&gt;兩邊放在一起看，結構性的錯位就浮出來了：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/no-backup-support-remote-work/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;學校的作息是照著一個白天有人在家的家庭設計的，公司的工時是照著一個家裡有人顧小孩的員工設計的&lt;/strong&gt;。這兩個假設要在同一個家庭裡同時成立，機率其實不高。&lt;/p&gt;
&lt;p&gt;雙薪早就是常態，但制度還停在雙薪不是常態的那個年代。&lt;/p&gt;
&lt;p&gt;順帶一提，留言區還有一位在荷蘭的家長說「我也都六點多才接」。所以這也不是台灣獨有的問題，只是台灣的通勤和加班文化把它放大了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;我的版本&quot;&gt;我的版本&lt;/h2&gt;
&lt;p&gt;我不是從那則貼文才認識這個處境的。我自己走過。&lt;/p&gt;
&lt;p&gt;大寶還在托嬰中心的時候，我每天推著娃娃車趕最早的七點半入托，把小孩交給老師再趕去上班；下班要趕在六點關門前接回來。加班就完蛋——延托費是小事，真正難受的是你推開門，全班的位子都空了，只剩你的小孩一個人坐在那裡等。（那段日子我寫在〈&lt;a href=&quot;https://bobochen.dev/blog/sandwich-gen-diary-23-remote-work-real-reason&quot;&gt;遠距工作的真正理由&lt;/a&gt;〉。）&lt;/p&gt;
&lt;p&gt;那時候有個念頭一直繞著我：我把小孩生出來，結果他一天裡最長的清醒時間，是跟別人在一起，不是跟我。&lt;/p&gt;
&lt;p&gt;而我的「沒有後援」，是兩種疊在一起的沒有。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一種是距離。&lt;/strong&gt; 長輩住外縣市。假日可以幫，臨時北上也可以幫，但「每個上班日下午四點去接小孩」這種每天都要發生的事，距離直接把它判出局。後援只有在能常態發生的時候才叫後援，一年來三次的那種叫探親。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;另一種更難講。&lt;/strong&gt; 2023 年我爸中風，接下來一年半，我不是那個有後援的人，我是別人的後援。（那一段在〈&lt;a href=&quot;https://bobochen.dev/blog/sandwich-gen-diary-17-hope-is-cruelest&quot;&gt;希望是最殘酷的&lt;/a&gt;〉和〈&lt;a href=&quot;https://bobochen.dev/blog/sandwich-gen-diary-18-monthly-38k&quot;&gt;每月三萬八&lt;/a&gt;〉。）&lt;/p&gt;
&lt;p&gt;這是三明治世代最少被講到的一件事：對很多人來說「有沒有後援」是一個加分題，有就加分，沒有就零分。對我們來說那一欄不是空白，是&lt;strong&gt;負數&lt;/strong&gt;。你不只沒有人幫你接小孩，你還要挪出時間去顧那個本來應該幫你接小孩的人。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;卡在這裡的人其實只有四條路&quot;&gt;卡在這裡的人，其實只有四條路&lt;/h2&gt;
&lt;p&gt;有趣的是，那則貼文的留言區已經把四條路都演出來了。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/no-backup-support-remote-work/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一條，買後援。&lt;/strong&gt; 阿公阿嬤同住、請保母、加錢延托。有留言講得很傳神：「這個要有小孩之後才會了解的，婚前一定嫌棄父母同住、長輩管太多。」代價不只是錢，還有你婚前很堅持的那些邊界。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二條，改工時。&lt;/strong&gt; 有位家長說「不想讓孩子當最後那幾個回家，有特別調整工作時間，每天趕五點前接」，然後自己接了一句：「但我時間的調整，相對也影響薪資。」代價寫在臉上，很誠實。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三條，改工作型態。&lt;/strong&gt; 「斜槓三年變正職，開始四點接小孩，超開心。」這是整個留言區裡唯一把這題結構性解掉的人。我走的也是這條。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四條，跟現實和解。&lt;/strong&gt; 「我放棄在意了。我決定讓小孩從小就知道你爸媽是這樣過生活的。」還有一位一打二的媽媽說：「我不覺得怎樣啊⋯⋯職業媽媽已經很辛苦了，不要情勒自己。」&lt;/p&gt;
&lt;p&gt;第四條不是投降。它是把「小孩最後一個被接走」從「我失職」重新定義成「我們家的生活方式」。心理成本不見得比較低，但它是誠實的。&lt;/p&gt;
&lt;p&gt;沒有第五條路。你能做的就是挑一條，然後接受它的代價。&lt;/p&gt;
&lt;p&gt;真正把人磨掉的，是四條都不挑：每天六點衝到校門口，一邊跟老師道歉，一邊怪自己，然後明天再來一次。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;我為什麼選第三條&quot;&gt;我為什麼選第三條&lt;/h2&gt;
&lt;p&gt;不是因為它最好，是因為前兩條對我不成立。&lt;/p&gt;
&lt;p&gt;第一條，長輩在外縣市，或者本身就是需要被照顧的那一方；花錢買保母可以，但當時的收入結構撐不住每個月多這一筆。&lt;/p&gt;
&lt;p&gt;第二條我試過。台灣職場所謂的彈性上下班，多半是三十分鐘的位移，把 9:00 挪成 8:30。它解決不了「16:00 放學」這件事。你需要的不是提早半小時，是那段時間的所有權。&lt;/p&gt;
&lt;p&gt;所以我要的不是彈性，是&lt;strong&gt;全遠距&lt;/strong&gt;。不是混合制、不是一週進辦公室兩天，是全遠距。因為只要有一天必須進辦公室，那一天你就得回頭去找後援——而你沒有。混合制對有後援的人是福利，對沒後援的人只是把難題從五天縮成兩天。&lt;/p&gt;
&lt;p&gt;這條路的代價我在系列前面寫過，這裡不重講：職缺選擇變少、升遷變慢（〈&lt;a href=&quot;https://bobochen.dev/blog/remote-work-first-year&quot;&gt;遠距工作第一年&lt;/a&gt;〉），工作會自動填滿所有縫隙、人在家裡但爸爸不在（〈&lt;a href=&quot;https://bobochen.dev/blog/remote-work-boundaries-for-family&quot;&gt;怎麼在遠距工作裡保持高效&lt;/a&gt;〉），以及兩頭都要顧的時間分配（〈&lt;a href=&quot;https://bobochen.dev/blog/sandwich-generation-career-design&quot;&gt;三明治世代的職涯設計&lt;/a&gt;〉）。&lt;/p&gt;
&lt;p&gt;我只補一句當時沒寫的：這個選擇最貴的地方不是薪水，是&lt;strong&gt;你主動把職涯的可選項砍掉一半，而且要砍很多年&lt;/strong&gt;。薪水少個幾成還算得出來，可選項變少這件事你要三五年後才會感覺到。&lt;/p&gt;
&lt;p&gt;我還是會選同一條。但我不想把它講成一個沒有代價的美好決定。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;給還卡在原地的人四件具體的事&quot;&gt;給還卡在原地的人：四件具體的事&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;一、先把「不可妥協時段」寫成一個時間區塊。&lt;/strong&gt; 不是「我想多陪小孩」，那太模糊、沒辦法拿去談。是「每個上班日 15:40 到 17:00，我要在校門口」。變成具體的時段，它才能跟工作條件對價。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二、把 remote 當篩選條件，不是加分項。&lt;/strong&gt; 搜職缺的時候用「全遠距」「fully remote」當硬條件，並且在第一輪就問清楚：一年要進辦公室幾次？有沒有「臨時請你來一趟」的文化？很多職缺在描述裡寫 remote，實際上是混合制——那解不了你的題。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三、面試時把它講成規格，不要講成請求。&lt;/strong&gt; 不要說「我需要四點去接小孩，這樣可以嗎」，那是在求通融，對方有權說不。要說「我的工作時段是 8:00–16:00 與 20:00–22:00，中間有一段固定離線，緊急事件的回應時間我會怎麼處理」。同一件事，後者是在對齊交付規格，談的是怎麼合作，不是要不要放你一馬。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;四、先算好你願意付多少，再去談。&lt;/strong&gt; 遠距職缺的薪資帶通常比較窄。先想清楚能接受降多少、打算幾年之內補回來。想清楚再談，你就不會在第二年開始後悔——後悔比降薪貴多了。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;最後&quot;&gt;最後&lt;/h2&gt;
&lt;p&gt;那則貼文十四萬次瀏覽，不是因為原 po 特別倒楣。&lt;/p&gt;
&lt;p&gt;是因為每個看到的人心裡都有那個畫面：亮著的燈、疊起來的椅子、最後一個坐在大廳等的孩子。&lt;/p&gt;
&lt;p&gt;我想講的只有一句：&lt;strong&gt;如果你也做不到四點出現在校門口，那不是你不夠努力&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那是一道算術題，而且題目本身就出得不公平。有後援的人在解一題，沒後援的人在解另一題。&lt;/p&gt;
&lt;p&gt;你能做的，是別再假裝那是同一題。然後從那四條路裡面，認真地挑一條。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.C52VSsLW.webp" medium="image"/><category>遠距工作</category><category>育兒</category><category>雙薪家庭</category><category>家庭支援</category><category>職涯選擇</category><category>三明治世代</category><enclosure url="https://bobochen.dev/_astro/cover.C52VSsLW.webp" length="0" type="image/png"/></item><item><title>一個從第一天就知道的資安問題,為什麼拖到客戶給我們一個 F 才修?</title><link>https://bobochen.dev/blog/reproduce-vendor-security-rating-f/</link><guid isPermaLink="true">https://bobochen.dev/blog/reproduce-vendor-security-rating-f/</guid><description>客戶委託第三方資安評等,我們的 email 認證拿了 F。但這類評等其實是被動 OSINT——只讀公開 DNS。我用 60 行 bash 重現了那個分數,順便聊繼承來的 tech debt 為什麼總是沒人修。</description><pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我接手公司主網域的維運時,它已經跑很多年了。email 的寄件認證——SPF、DKIM、DMARC——從草創期就沒設好。不是我搞砸的,是更早的「古人」留下的;那些人離職多久已經不可考,當初為什麼這樣設、有沒有什麼理由,也隨著他們一起消失了。&lt;/p&gt;
&lt;p&gt;團隊其實「知道」這件事。它是那種會在閒聊時被提起、大家點點頭、然後沒有人把它寫進 sprint 的 folklore。它太低調了:不會讓服務掛掉、不會跳 alert、不會有人因為它半夜被 call。所以它就這樣躺著,躺了好幾年。&lt;/p&gt;
&lt;p&gt;直到有一天,一個大客戶委託第三方資安評等廠商掃了我們,email 項目給了一個 &lt;strong&gt;F&lt;/strong&gt;——百分位數幾乎墊底。&lt;/p&gt;
&lt;p&gt;這篇想講的不是「怎麼設 DMARC」(網路上一堆)。而是兩件更有意思的事:&lt;strong&gt;為什麼一個大家都知道的問題能躺這麼久&lt;/strong&gt;,以及我發現的——&lt;strong&gt;那個嚇人的 F,其實是你可以自己重現、自己量測的分數&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;📎 &lt;strong&gt;系列文「誰在冒用你的 Domain 寄信」&lt;/strong&gt;:這是系列起點(案例篇)。完全不熟 SPF/DKIM/DMARC?先看 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 白話入門篇&lt;/a&gt;;想直接動手把 DMARC 推到 &lt;code&gt;p=reject&lt;/code&gt;,看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 實戰 playbook&lt;/a&gt;;真的按下去那天發生什麼事,在 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4 執行日的假訊號&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;背景繼承來的沒人排程的債&quot;&gt;背景:繼承來的、沒人排程的債&lt;/h2&gt;
&lt;p&gt;第三方資安評等(RiskRecon、BitSight、SecurityScorecard 這類)現在很常見。大客戶在簽約前或定期稽核時,會請這種廠商幫你「打分數」,然後把報告丟給你。一個對外可見的 F,在這種情境下特別刺眼——因為打分的是你的客戶。&lt;/p&gt;
&lt;p&gt;但我接手的這個 email 認證問題有個特性:&lt;strong&gt;它是繼承來的&lt;/strong&gt;。我沒設定它,我甚至不認識設定它的人。這種「孤兒 tech debt」最危險的地方在於——&lt;strong&gt;沒有人覺得自己該為它負責&lt;/strong&gt;。新人覺得「這是以前就這樣」,老人走了,於是它變成一塊沒有 owner 的地。&lt;/p&gt;
&lt;h2 id=&quot;發現過程先別修先搞懂它怎麼算的&quot;&gt;發現過程:先別修,先搞懂它怎麼算的&lt;/h2&gt;
&lt;p&gt;我的第一反應本來是直接去改 DNS。但我停了一下,問自己一個問題:&lt;strong&gt;這個 F 到底是怎麼算出來的?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;關鍵的領悟是:這類評等是 &lt;strong&gt;被動 OSINT(open-source intelligence)&lt;/strong&gt;。報告裡白紙黑字寫著——它不碰你的系統、不打你的 API、不猜密碼。它只是從外面讀你的&lt;strong&gt;公開 DNS 記錄&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這跟大部分人的直覺相反。我第一次看到「被掃了」的時候,也以為對方在打我們的伺服器,還想說防火牆 log 裡應該找得到痕跡。並沒有——它連門都沒碰:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;也就是說,它看得到的東西,我用一支 &lt;code&gt;dig&lt;/code&gt; 也看得到。於是我把它會看的訊號全查了一遍:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;dig +short TXT example.com            # SPF：發現是 &quot;?all&quot;（neutral，等於沒防護）
dig +short TXT _dmarc.example.com     # DMARC：發現是 &quot;p=none&quot;（只監控、不執行）
dig +short TXT google._domainkey.example.com   # 主要寄信的 DKIM：沒設
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每一條都對得上報告的扣分點。既然如此,我乾脆寫了一支 &lt;strong&gt;60 行的 bash&lt;/strong&gt;,用它報告裡列出的同一批訊號、自己配一組權重,算一個分數出來。&lt;/p&gt;
&lt;p&gt;跑完——&lt;strong&gt;3/10,F&lt;/strong&gt;。官方給的是 3.5。&lt;strong&gt;幾乎一模一樣。&lt;/strong&gt; 廠商真正的加權演算法是專有的,我配的權重只是逼近——但至少我不用再等它下一份報告,就能自己量一次。&lt;/p&gt;
&lt;p&gt;過程中還有兩個額外收穫:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;報告會過時。&lt;/strong&gt; 它的掃描日跟我讀它的那天差了兩週。我用 &lt;code&gt;dig&lt;/code&gt;/&lt;code&gt;openssl&lt;/code&gt; 實測,發現有些被扣分的項目其實早就修好了。&lt;strong&gt;實測永遠比讀報告可靠。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;恐慌是不必要的。&lt;/strong&gt; 我把實際在寄信的管道一個個查清楚,發現真正高流量的寄信來源(交易信、行銷信)其實早就認證對齊了。缺口比 F 看起來小得多。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;具體數據--結果&quot;&gt;具體數據 / 結果&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;自製腳本 vs 官方分數&lt;/strong&gt;:3/10 vs 3.5/10,差 0.5 分,等級同樣落在 F。足以拿來當免費、即時的對照工具。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;email 認證的三個關鍵旋鈕&lt;/strong&gt;(這就是評分的核心):&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;紀錄&lt;/th&gt;&lt;th&gt;弱(會被扣分)&lt;/th&gt;&lt;th&gt;強(拿分)&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;SPF&lt;/td&gt;&lt;td&gt;&lt;code&gt;?all&lt;/code&gt;(neutral,不防偽)&lt;/td&gt;&lt;td&gt;&lt;code&gt;-all&lt;/code&gt;(hardfail)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DMARC&lt;/td&gt;&lt;td&gt;&lt;code&gt;p=none&lt;/code&gt;(只監控)&lt;/td&gt;&lt;td&gt;&lt;code&gt;p=reject&lt;/code&gt;(強制)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DKIM&lt;/td&gt;&lt;td&gt;主要寄件管道沒簽章&lt;/td&gt;&lt;td&gt;有簽章且對齊&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;同一件事換成腳本的算法,就是下面這張計分表。它也解釋了我們為什麼剛好卡在 3 分:&lt;/p&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;檢查項目&lt;/th&gt;&lt;th&gt;查法&lt;/th&gt;&lt;th&gt;配分&lt;/th&gt;&lt;th&gt;我們當時&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;SPF 結尾機制&lt;/td&gt;&lt;td&gt;&lt;code&gt;dig +short TXT 網域&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;-all&lt;/code&gt; 3 / &lt;code&gt;~all&lt;/code&gt; 2 / &lt;code&gt;?all&lt;/code&gt; 1 / 沒有 0&lt;/td&gt;&lt;td&gt;&lt;code&gt;?all&lt;/code&gt; → 1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DKIM 公鑰&lt;/td&gt;&lt;td&gt;&lt;code&gt;dig +short TXT google._domainkey.網域&lt;/code&gt;&lt;/td&gt;&lt;td&gt;查得到 1 / 查不到 0&lt;/td&gt;&lt;td&gt;沒設 → 0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DMARC 政策&lt;/td&gt;&lt;td&gt;&lt;code&gt;dig +short TXT _dmarc.網域&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;reject&lt;/code&gt; 5 / &lt;code&gt;quarantine&lt;/code&gt; 3 / &lt;code&gt;none&lt;/code&gt; 1 / 沒有 0&lt;/td&gt;&lt;td&gt;&lt;code&gt;p=none&lt;/code&gt; → 1&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;MX 指向託管信箱&lt;/td&gt;&lt;td&gt;&lt;code&gt;dig +short MX 網域&lt;/code&gt;&lt;/td&gt;&lt;td&gt;是 1 / 否 0&lt;/td&gt;&lt;td&gt;Google Workspace → 1&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;畫成格子更有感——&lt;strong&gt;滿分 10 格裡,DMARC 一筆 TXT 記錄就佔了 5 格&lt;/strong&gt;:&lt;/p&gt;
&lt;figure style=&quot;margin:1.75em 0&quot;&gt;

&lt;figcaption style=&quot;font-size:0.85em;opacity:0.65;text-align:center;margin-top:0.5em&quot;&gt;缺的 7 格裡有 4 格在 DMARC 那一列——所以先修 DMARC,分數跳得最明顯。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;可預期的修復軌跡&lt;/strong&gt;:因為 DMARC 強制要分階段上線(先監控、再 quarantine、最後 reject),分數會&lt;strong&gt;一階一階往上爬&lt;/strong&gt;,而不是一次到位:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/reproduce-vendor-security-rating-f/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;那支腳本(去識別化版)&lt;/strong&gt;,你可以直接拿去量自己的網域:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
# email-auth-scorecard.sh — 用公開 DNS 重現「email 資安評等」分數
D=&quot;${1:-example.com}&quot;
score=0

# ① SPF(0-3 分):撈出 TXT 裡 v=spf1 那一筆,只看結尾的 all 機制有多寬鬆
spf=&quot;$(dig +short TXT &quot;$D&quot; | tr -d &apos;&quot;&apos; | grep -i &apos;v=spf1&apos; | head -1)&quot;
case &quot;$spf&quot; in
  *&quot;-all&quot;*) score=$((score+3));; *&quot;~all&quot;*) score=$((score+2));;
  *&quot;?all&quot;*) score=$((score+1));; esac

# ② DKIM(0-1 分):查得到公鑰就算有簽章。google 是 Google Workspace 的 selector,
#    換寄件管道要換名字(Mailgun 常見 mg、SendGrid 是 s1)
[ -n &quot;$(dig +short TXT google._domainkey.&quot;$D&quot;)&quot; ] &amp;#x26;&amp;#x26; score=$((score+1))

# ③ DMARC(0-5 分):權重最重的一項,一筆 TXT 就佔掉滿分的一半
dmarc=&quot;$(dig +short TXT _dmarc.&quot;$D&quot; | tr -d &apos;&quot;&apos;)&quot;
case &quot;$dmarc&quot; in
  *reject*) score=$((score+5));; *quarantine*) score=$((score+3));;
  *p=none*) score=$((score+1));; esac

# ④ MX(0-1 分):有指向託管信箱,當作「這個網域真的在收信、有人在管」
dig +short MX &quot;$D&quot; | grep -qiE &apos;google|aspmx&apos; &amp;#x26;&amp;#x26; score=$((score+1))

echo &quot;$D → $score/10&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3 分鐘上手&lt;/strong&gt;:① 存成 &lt;code&gt;email-auth-scorecard.sh&lt;/code&gt; ② &lt;code&gt;chmod +x email-auth-scorecard.sh&lt;/code&gt; ③ &lt;code&gt;./email-auth-scorecard.sh yourdomain.com&lt;/code&gt;。回傳 0–10 分;想交叉驗證可再丟進 &lt;a href=&quot;https://internet.nl&quot;&gt;internet.nl&lt;/a&gt;、&lt;a href=&quot;https://www.hardenize.com&quot;&gt;Hardenize&lt;/a&gt;、或寄封測試信到 &lt;a href=&quot;https://www.mail-tester.com&quot;&gt;mail-tester.com&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;順帶一提:前置步驟(補 SPF、開 DKIM、DMARC 先開監控)其實&lt;strong&gt;零風險&lt;/strong&gt;——它們只是「多授權」,不會擋掉任何信。唯一有風險的是最後把 DMARC 切到強制,而那步被監控期擋著。所以「F」聽起來嚇人,實際動工的爆炸半徑很小。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;反思&quot;&gt;反思&lt;/h2&gt;
&lt;h3 id=&quot;技術面&quot;&gt;技術面&lt;/h3&gt;
&lt;p&gt;第三方資安評等不是魔法——至少 email 這一項,它讀的就是&lt;strong&gt;任何人都查得到的那幾筆 DNS 記錄&lt;/strong&gt;。一旦你知道它只讀 DNS,那個字母分數就從「客戶丟給你的判決書」變成「一個你能自己重現、自己追蹤的數字」。把黑箱變成儀表板,修起來才有對照、有信心。&lt;/p&gt;
&lt;h3 id=&quot;心態面&quot;&gt;心態面&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;繼承來的債最容易沒人修。&lt;/strong&gt; 不是因為難,是因為沒有 owner。設定它的人走了,institutional knowledge 跟著走,剩下的人都覺得「這不是我弄的」。孤兒 tech debt 會一直是孤兒,直到出現一個逼它認領的理由。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「知道」不等於「會修」。&lt;/strong&gt; 我們知道這問題很多年。讓它終於上線的不是內部的自覺,而是一個&lt;strong&gt;外部、可見、有人盯著&lt;/strong&gt;的 forcing function——客戶的分數。這不太光彩,但很真實。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;所以,與其等外部羞辱,不如自己動手讓債「可見」。&lt;/strong&gt; 那支 60 行腳本最大的價值不是算分,是它把一塊抽象的、沒人理的 tech debt,變成主管會議上一句「我們現在 3/10,改完會到 10/10」。&lt;strong&gt;可見性,就是被排程的機率。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;有趣發現&quot;&gt;有趣發現&lt;/h3&gt;
&lt;p&gt;最諷刺的一點:我原本只是想搞懂報告怎麼算,結果「自己重現分數」這件事,意外成了整個專案最有效的&lt;strong&gt;溝通工具&lt;/strong&gt;。一份 20 頁的 PDF 很難推動排程;但一個會跳動的數字、一條看得到終點的軌跡,讓「修這個」第一次變得有吸引力。&lt;/p&gt;
&lt;p&gt;技術債從來不缺「知道的人」,缺的是讓它&lt;strong&gt;被看見&lt;/strong&gt;的人。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;如果讀到這裡,還是不太確定 SPF、DKIM、DMARC 各自在管什麼,系列 &lt;a href=&quot;https://bobochen.dev/blog/spf-dkim-dmarc-explained/&quot;&gt;#2 白話入門篇&lt;/a&gt; 用「寄一封實體掛號信」的比喻把三個一次講完;已經懂了、想直接動手把 &lt;code&gt;p=none&lt;/code&gt; 推到 &lt;code&gt;p=reject&lt;/code&gt;,接 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-none-to-reject-playbook/&quot;&gt;#3 實戰 playbook&lt;/a&gt;;想先看看真的按下去那天會遇到什麼,看 &lt;a href=&quot;https://bobochen.dev/blog/dmarc-enforcement-day-false-signals/&quot;&gt;#4 執行日的假訊號&lt;/a&gt;。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/images/og/reproduce-vendor-security-rating-f.png" medium="image"/><category>資安</category><category>tech-debt</category><category>email-security</category><category>DMARC</category><category>vendor-risk</category><enclosure url="https://bobochen.dev/images/og/reproduce-vendor-security-rating-f.png" length="0" type="image/png"/></item><item><title>安養、養護、護理之家差在哪？一張圖看懂台灣長照機構的類型</title><link>https://bobochen.dev/blog/ltc-facility-types-guide/</link><guid isPermaLink="true">https://bobochen.dev/blog/ltc-facility-types-guide/</guid><description>2023 年爸中風倒下，我以為「養老院」就是養老院。實際跑一輪才知道，光是收住長輩的機構就分成五種，法源、主管機關、能收的對象、法定人力比全都不一樣，送錯是會被拒收的。這篇用我當年畫的那張心智圖，把台灣長照機構的類型一次講清楚——包含最實用的「插幾管」判斷法，以及我那張圖後來發現畫錯的地方。</description><pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;安老院是一個誤會&quot;&gt;「安老院」是一個誤會&lt;/h2&gt;
&lt;p&gt;2023 年春天，爸腦出血倒在家裡，開完刀、住完加護病房、轉到一般病房之後，醫院社工問我：「你們出院之後有什麼規劃？」&lt;/p&gt;
&lt;p&gt;我有點狀況外的回說：「應該會送安老院吧。」&lt;/p&gt;
&lt;p&gt;對方停了一下，說：「你爸這個狀況，安老院不能收喔。」&lt;/p&gt;
&lt;p&gt;那是我第一次知道，「安老院」不是一種地方。它是一整條光譜，從「長輩其實還很硬朗、只是需要有人照應」一路到「插著三條管、要 24 小時有護理人員」，中間分成好幾種，&lt;strong&gt;每一種的法源、主管機關、能收的人、法定人力配置都不一樣&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;更麻煩的是，送錯的下場通常不是「換一家就好」。你可能白跑一趟、被機構當場拒收；或者更糟——收進去了，但那家機構本來就沒有能力處理你家長輩的狀況。&lt;/p&gt;
&lt;p&gt;所以我花了幾天，把整件事搞懂，然後畫了一張圖。&lt;/p&gt;
&lt;h2 id=&quot;我當年畫的那張圖&quot;&gt;我當年畫的那張圖&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://bobochen.dev/images/ltc-facility-types/mindmap.webp&quot; alt=&quot;長照機構分類心智圖：長照機構分為安養機構、老人長期照顧中心、護理之家三大分支。安養機構別名養老院，適合身體健康的長者；老人長期照顧中心別名養護機構或養護中心，適合插兩管，再分為養護型（收容有意識但需要協助生活行為的長者）與長期照顧型（有慢性病、長期醫療服務需求的長者）；護理之家適合插三管，有氣切才需要送這邊。右側註記照護需求程度由輕到重的排序。圖中紅框標記「爸爸適合送老人長期照顧中心（養護型）」&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2023 年我跑完機構、打完一輪詢價電話之後，為了讓自己和家人搞清楚而畫的。紅框那個「老人長期照顧中心（養護型）」，就是爸後來安老的地方。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;先說在前面：這張圖是我當年的理解，&lt;strong&gt;它有兩個地方和法規的正式寫法對不上&lt;/strong&gt;，我在文章後面會誠實交代。但它抓住的那條主軸——照護需求由輕到重排成一條線——是對的，而且這條線就是你要做的第一個判斷。&lt;/p&gt;
&lt;h2 id=&quot;先搞懂這其實是兩套系統&quot;&gt;先搞懂：這其實是兩套系統&lt;/h2&gt;
&lt;p&gt;在拆解類型之前，有一件事我希望當初有人先告訴我，它能解釋你查資料時感受到的所有混亂：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這些機構分屬兩套完全不同的法規體系。&lt;/strong&gt;&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;老人福利機構&lt;/th&gt;&lt;th&gt;護理之家&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;法源&lt;/td&gt;&lt;td&gt;老人福利法／老人福利機構設立標準&lt;/td&gt;&lt;td&gt;護理人員法／護理機構分類設置標準&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;主管機關&lt;/td&gt;&lt;td&gt;社會福利主管機關（社會局／處）&lt;/td&gt;&lt;td&gt;衛生主管機關（衛生局）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;白話&lt;/td&gt;&lt;td&gt;社政體系&lt;/td&gt;&lt;td&gt;衛政體系&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;包含&lt;/td&gt;&lt;td&gt;安養機構、長期照顧機構（三型）&lt;/td&gt;&lt;td&gt;一般／精神／產後護理之家&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;兩套法、兩個主管機關、兩份機構名冊、兩套評鑑。&lt;/p&gt;
&lt;p&gt;這就是為什麼你 Google「長照機構」會查到一堆互相矛盾的分類表——因為有些文章只講社政那半邊，有些只講衛政那半邊，而且新舊法規的名詞還混用。搞清楚這件事之後，很多混亂會自己消失，包括後面「該去哪裡查合格機構」也是分兩邊查的。&lt;/p&gt;
&lt;h2 id=&quot;五種類型逐一拆解&quot;&gt;五種類型，逐一拆解&lt;/h2&gt;
&lt;p&gt;依照現行的《老人福利機構設立標準》第 2 條，老人福利機構分為三大類：&lt;strong&gt;長期照顧機構、安養機構、其他老人福利機構&lt;/strong&gt;。而長期照顧機構底下又分三型。加上衛政那邊的護理之家，實務上你會遇到的就是下面這五種。&lt;/p&gt;
&lt;p&gt;我把法規的定義原文放進來，因為這是你判斷「我家長輩該送哪一種」最硬的依據：&lt;/p&gt;
&lt;h3 id=&quot;安養機構&quot;&gt;安養機構&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;照顧需他人照顧或無扶養義務親屬或扶養義務親屬無扶養能力，且日常生活能自理之老人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;關鍵字是&lt;strong&gt;日常生活能自理&lt;/strong&gt;。這就是大家俗稱的「養老院」——長輩自己吃飯、自己上廁所、自己走動，機構提供的是住宿、伙食、生活照應和社交。&lt;/p&gt;
&lt;p&gt;我爸昏迷開刀、半身不遂、插著鼻胃管躺在床上，離「日常生活能自理」有多遠，你可以想像。所以社工才會說「安老院不能收」——不是機構挑客人，是法規上他們本來就不是收這種對象的。&lt;/p&gt;
&lt;h3 id=&quot;長期照顧機構養護型&quot;&gt;長期照顧機構——養護型&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;照顧生活自理能力缺損需他人照顧之老人或需鼻胃管、胃造廔口、導尿管護理服務需求之老人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;看到了嗎？法規的定義原文&lt;strong&gt;直接點名了鼻胃管和導尿管&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這一型就是我圖上那個紅框，也是絕大多數家屬最後會落到的地方。招牌上你會看到它叫「養護中心」「養護之家」「老人長期照顧中心」，講的都是同一件事。&lt;/p&gt;
&lt;p&gt;爸插著鼻胃管、尿管，半身不遂、失語、臥床，需要人翻身餵食換尿布，但沒有氣切、不用呼吸器——這個組合，標準答案就是養護型。&lt;/p&gt;
&lt;h3 id=&quot;長期照顧機構長期照護型&quot;&gt;長期照顧機構——長期照護型&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;照顧罹患長期慢性病，且需要醫護服務及他人照顧之老人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;和養護型的差別在「&lt;strong&gt;需要醫護服務&lt;/strong&gt;」這五個字。同樣是臥床、同樣要人照顧，但如果長輩身上有需要持續醫療處置的慢性病，照護強度就往上跳一階。這一階的法定護理人力配置也確實比養護型高（後面那張表會看到）。&lt;/p&gt;
&lt;h3 id=&quot;長期照顧機構失智照顧型&quot;&gt;長期照顧機構——失智照顧型&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;照顧神經科、精神科或其他專科醫師診斷為失智症中度以上、具行動能力，且需受照顧之老人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;這一型是我當年那張圖完全漏掉的&lt;/strong&gt;，因為爸不是失智型，我就沒去了解。但如果你家長輩的狀況是「中度以上失智、但還能走能動」，那你要找的是這一型，不是養護型。&lt;/p&gt;
&lt;p&gt;注意定義裡的「具行動能力」——這正是它照顧起來最難的原因：長輩會遊走、會走失、會做出危險動作，需要的是有人一直盯著，而不是有人幫忙翻身。所以它的法定照服員人力比是所有類型裡最密集的，日間 1 比 3。&lt;/p&gt;
&lt;h3 id=&quot;護理之家一般護理之家&quot;&gt;護理之家（一般護理之家）&lt;/h3&gt;
&lt;p&gt;服務對象是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一、罹患慢性病需長期護理之病人。&lt;br&gt;
二、出院後需繼續護理之病人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;護理之家屬於衛政體系，最核心的差別是——&lt;strong&gt;24 小時都必須有護理人員值班&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;有氣切、需要頻繁抽痰、使用呼吸器、傷口需要專業換藥、管路複雜，這些是護理等級的事，養護中心做不來，要送護理之家。（順帶一提，「護理之家」在法規上還包含精神護理之家和產後護理之家——我們熟悉的月子中心，正式名稱多半就是產後護理之家。同一個詞、完全不同的東西，這也是混亂的來源之一。）&lt;/p&gt;
&lt;h2 id=&quot;最快的判斷法插幾管&quot;&gt;最快的判斷法：插幾管&lt;/h2&gt;
&lt;p&gt;法規定義讀起來很累。我當年整理出來、實際打電話問機構時最好用的一把尺，就是圖上那個：&lt;strong&gt;插幾管&lt;/strong&gt;。&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;狀況&lt;/th&gt;&lt;th&gt;該找的類型&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;沒插管，吃飯上廁所都自己來&lt;/td&gt;&lt;td&gt;安養機構&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;兩管&lt;/strong&gt;（鼻胃管＋導尿管），意識在，但生活行為需要人協助&lt;/td&gt;&lt;td&gt;養護型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;慢性病纏身，需要持續的醫護處置&lt;/td&gt;&lt;td&gt;長期照護型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;中度以上失智，還能走動、會遊走&lt;/td&gt;&lt;td&gt;失智照顧型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;三管&lt;/strong&gt;（多一條氣切管），要頻繁抽痰、用呼吸器&lt;/td&gt;&lt;td&gt;護理之家&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;所謂「兩管」，指的是鼻胃管（灌食）和導尿管；「三管」是再多一條氣切管。&lt;/p&gt;
&lt;p&gt;把上面的條件串成一次初步篩選，大致會這樣走：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/ltc-facility-types-guide/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這張圖只負責把你帶到「該先問哪一類機構」，最後仍要由醫院與機構依個案狀況確認，不是在線上替長輩做醫療或收住判定。&lt;/p&gt;
&lt;p&gt;我要特別講一下：「兩管送養護、三管送護理之家」聽起來像坊間口訣，但它其實有法規根據——養護型的定義原文自己就寫著「需鼻胃管、胃造廔口、導尿管護理服務需求之老人」，而氣切、抽痰、呼吸器屬於需要護理人員全時在場的處置，對應的就是護理之家那條「24 小時均應有護理人員值班」的規定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但這把尺是拿來快速定位、縮小範圍用的，不是拿來取代專業判斷的。&lt;/strong&gt; 真正的做法永遠是：出院前跟醫院申請病歷摘要，帶著它去問機構「我爸是這個狀況，你們收不收」。好的機構會誠實告訴你適不適合。&lt;/p&gt;
&lt;h2 id=&quot;用法定人力比看穿差別&quot;&gt;用「法定人力比」看穿差別&lt;/h2&gt;
&lt;p&gt;如果你想更具體地理解這幾種類型到底差在哪，看人力配置最準。這是《老人福利機構設立標準》和《護理機構分類設置標準》規定的&lt;strong&gt;法定最低配置&lt;/strong&gt;：&lt;/p&gt;









































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;類型&lt;/th&gt;&lt;th&gt;護理人員&lt;/th&gt;&lt;th&gt;照顧服務員（日間）&lt;/th&gt;&lt;th&gt;照顧服務員（夜間）&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;安養機構&lt;/td&gt;&lt;td&gt;隨時至少 1 人上班&lt;/td&gt;&lt;td&gt;1 : 15&lt;/td&gt;&lt;td&gt;1 : 35&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;養護型&lt;/td&gt;&lt;td&gt;1 : 20&lt;/td&gt;&lt;td&gt;1 : 8&lt;/td&gt;&lt;td&gt;1 : 25&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;長期照護型&lt;/td&gt;&lt;td&gt;1 : 15&lt;/td&gt;&lt;td&gt;1 : 5&lt;/td&gt;&lt;td&gt;1 : 15&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;失智照顧型&lt;/td&gt;&lt;td&gt;1 : 20&lt;/td&gt;&lt;td&gt;1 : 3&lt;/td&gt;&lt;td&gt;1 : 15&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;一般護理之家&lt;/td&gt;&lt;td&gt;每 15 床至少 1 人，且 24 小時均應有護理人員值班&lt;/td&gt;&lt;td&gt;編制上每 5 床至少 1 人（不分班別）；任何時段護理人員與照顧服務員總數與住民比不得低於 1 : 15&lt;/td&gt;&lt;td&gt;同左（不分日夜班）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;（法規定義的日間是上午 8 時至下午 8 時，夜間是下午 8 時至翌日上午 8 時。）&lt;/p&gt;
&lt;p&gt;這張表有兩個用法。&lt;/p&gt;
&lt;p&gt;第一，它讓「照護強度由輕到重」變成看得見的數字：安養機構夜間一個照服員顧 35 人，養護型 25 人，長期照護型和失智照顧型 15 人。你家長輩半夜需要多久被看一次，這裡就寫著答案。&lt;/p&gt;
&lt;p&gt;第二——這點更重要——&lt;strong&gt;這是法定下限，不是實際水準&lt;/strong&gt;。合格機構只會比它好，不會比它差（比它差就是違法）。所以當你參觀時問「你們一個照服員顧幾床、夜班幾個人」，如果對方報出來的數字剛好貼著上表，你就知道這家是踩在底線上經營。這是同一句回答，你有沒有這張表，聽到的意思完全不同。&lt;/p&gt;
&lt;h2 id=&quot;我那張圖畫錯的地方&quot;&gt;我那張圖畫錯的地方&lt;/h2&gt;
&lt;p&gt;寫到這裡，該回頭認錯了。三年後重新查法規，我當年那張圖有三個問題：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一、名稱一字之差。&lt;/strong&gt; 我寫的是「長期照顧型」，法規的正式名稱是「長期&lt;strong&gt;照護&lt;/strong&gt;型」。長期照顧機構底下分「長期照護型／養護型／失智照顧型」——外層叫照顧、內層那一型叫照護。這種命名確實很容易混，但你去問機構、查名冊時用錯字會查不到東西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二、漏了一整型。&lt;/strong&gt; 圖上只有兩型，法規有三型，我漏掉的是&lt;strong&gt;失智照顧型&lt;/strong&gt;。原因很單純：爸不是失智，我當時只查對我有用的部分。這正是「當事人筆記」的極限——它只涵蓋你踩過的那條路。如果你家長輩是失智，我這張圖會直接把你帶偏。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三、別名其實比圖上更亂。&lt;/strong&gt; 圖上我寫「養護機構、養護中心」是「老人長期照顧中心」的別名，這個理解方向沒錯，但實際情況更混亂：舊法規用的是「長期照護機構／養護機構」這套名詞，現行法規改成「長期照顧機構」底下分型，而市面上的招牌則是想怎麼取就怎麼取。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;結論是：看招牌沒有用。&lt;/strong&gt; 要看的是機構的&lt;strong&gt;設立許可證上登記的是哪一類、哪一型&lt;/strong&gt;——這才是決定他們合法能收什麼人的東西。參觀時直接開口問、要求看，正派的機構不會不給。&lt;/p&gt;
&lt;h2 id=&quot;送錯會怎樣以及狀況會變&quot;&gt;送錯會怎樣，以及狀況會變&lt;/h2&gt;
&lt;p&gt;實務上最常發生的不是「送錯」，而是&lt;strong&gt;被拒收&lt;/strong&gt;——你興沖沖跑去，對方看完病歷摘要說「這個我們收不了」。所以先問再跑，別先跑再問。&lt;/p&gt;
&lt;p&gt;比拒收更需要提前想的是另一件事：&lt;strong&gt;你家長輩的狀況會變&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;爸住進養護中心的時候是兩管。如果他後來惡化到需要氣切，那就不是「機構配合一下」的問題了，是他整個人要轉到護理之家去。這種事你不會希望在狀況最緊急的那一天才第一次思考。&lt;/p&gt;
&lt;p&gt;所以參觀的時候，除了問收不收，多問一句：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「如果他之後狀況變差、需要氣切或呼吸器，你們收到什麼程度？需要轉出去的時候，你們會協助嗎？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;會認真回答這題的機構，通常也是照顧上比較實在的那種。&lt;/p&gt;
&lt;h2 id=&quot;怎麼查合格機構&quot;&gt;怎麼查合格機構&lt;/h2&gt;
&lt;p&gt;因為分屬兩套系統，查的地方也是兩邊：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;衛福部長照專區（1966.gov.tw）&lt;/strong&gt; — 有長期照顧服務機構的評鑑結果公告。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;長照服務資源地理地圖&lt;/strong&gt; — 衛福部的單一入口，可以用地圖找附近的住宿式機構，並附評鑑等級。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;社家署（sfaa.gov.tw）機構查詢&lt;/strong&gt; — 老人福利機構名冊。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;各縣市社會局／處&lt;/strong&gt; — 老人福利機構（安養、養護型、長期照護型、失智照顧型）的名冊與評鑑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;各縣市衛生局&lt;/strong&gt; — 護理之家的名冊與評鑑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;打 1966 長照專線&lt;/strong&gt;（前 5 分鐘免費）— 不確定該找哪一種、該問誰的時候，這是最省事的起點。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⚠️ 最後提醒：&lt;strong&gt;本文引用的法規以撰文當下的版本為準&lt;/strong&gt;（《老人福利機構設立標準》最近一次修正為民國 113 年 8 月 22 日），法規會修、各縣市的執行細節也會有差異。真的要做決定前，請打 1966 或各縣市 1999 確認，不要憑網路上任何一篇文章（包括這篇）就下判斷。&lt;/p&gt;
&lt;h2 id=&quot;過來人提醒&quot;&gt;過來人提醒&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先分清楚兩套系統。&lt;/strong&gt; 老人福利機構（安養／養護型／長期照護型／失智照顧型）歸社政、社會局管；護理之家歸衛政、衛生局管。查名冊、查評鑑都要去對的那一邊，否則你會覺得資料到處都查不到。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用「插幾管」快速定位。&lt;/strong&gt; 沒插管能自理 → 安養；兩管（鼻胃管＋尿管）→ 養護型；三管（多氣切）、要頻繁抽痰 → 護理之家。中度以上失智但還能走動 → 失智照顧型，別誤送養護型。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;這把尺只能縮小範圍，不能取代專業判斷。&lt;/strong&gt; 出院前向醫院申請&lt;strong&gt;病歷摘要&lt;/strong&gt;，帶著它去問機構「我家長輩這個狀況，你們收不收」。先問再跑，省下一半的路。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;看招牌沒用，要看設立許可證上登記的類型。&lt;/strong&gt; 「養護中心」「養護之家」「老人長期照顧中心」都可能是同一型，也可能不是。直接開口問、要求看許可證。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把法定人力比背在身上再去參觀。&lt;/strong&gt; 養護型日間 1:8、夜間 1:25；長期照護型 1:5 / 1:15。這是下限不是水準——當機構報的數字剛好貼著下限，你就知道那代表什麼。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;問「狀況變差之後怎麼辦」。&lt;/strong&gt; 你家長輩的醫療需求會變，今天的兩管可能變成明天的三管。提前問清楚機構收到什麼程度、需要轉出時會不會協助。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不確定就打 1966。&lt;/strong&gt; 前 5 分鐘免費。與其在網路上比對十篇互相矛盾的分類文章，不如讓專線幫你定位。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;我把挑機構之後的下一步——&lt;strong&gt;怎麼參觀、怎麼詢價、帳單上那一堆「另計」項目怎麼問&lt;/strong&gt;——寫在〈&lt;a href=&quot;https://bobochen.dev/blog/longterm-care-notes-05-choosing-a-facility&quot;&gt;挑機構、問價格：養護中心還是護理之家&lt;/a&gt;〉。那篇有一份可以照著打電話的詢價清單，因為月費從來都不是你最後要付的數字。&lt;/p&gt;
&lt;p&gt;整個歷程（從爸倒下、加護病房、出院準備、第一次打 1966，到補助迷宮）則收在〈&lt;a href=&quot;https://bobochen.dev/blog/series/%E9%95%B7%E7%85%A7%E6%96%B0%E6%89%8B%E7%AD%86%E8%A8%98&quot;&gt;長照新手筆記&lt;/a&gt;〉這個系列裡。那是我希望當初有人遞給我的那本說明書。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.C9Xy2zvg.webp" medium="image"/><category>長照</category><category>長照機構</category><category>養護中心</category><category>護理之家</category><category>安養機構</category><category>照顧者</category><category>出院準備</category><enclosure url="https://bobochen.dev/_astro/cover.C9Xy2zvg.webp" length="0" type="image/png"/></item><item><title>心電圖存成 DICOM 之後，要用什麼打開？</title><link>https://bobochen.dev/blog/ecg-dicom-viewer-weasis/</link><guid isPermaLink="true">https://bobochen.dev/blog/ecg-dicom-viewer-weasis/</guid><description>12 導程心電圖存成 DICOM 檔後，大部分 DICOM viewer 都打不開。這篇記錄 SOP Class UID 如何決定檔案是「圖」還是「波形」，以及開源工具 Weasis 的 ECG 檢視實際操作。</description><pubDate>Wed, 05 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;起因&quot;&gt;起因&lt;/h2&gt;
&lt;p&gt;原本只是要確認某個匯出的心電圖 DICOM 檔裡，那段診斷文字是不是正確的，但我用手邊的 DICOM viewer 打開，畫面一片空白。檔案其實沒壞，是工具不對 XD&lt;/p&gt;
&lt;h2 id=&quot;dicom-不是一種檔案&quot;&gt;DICOM 不是「一種」檔案&lt;/h2&gt;
&lt;p&gt;副檔名都是 &lt;code&gt;.dcm&lt;/code&gt;，裡面可能是完全不同的東西。決定它是什麼的是 &lt;strong&gt;SOP Class UID&lt;/strong&gt;：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;SOP Class UID&lt;/th&gt;&lt;th&gt;這是什麼&lt;/th&gt;&lt;th&gt;資料長什麼樣&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;1.2.840.10008.5.1.4.1.1.7&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Secondary Capture&lt;/td&gt;&lt;td&gt;像素。通常是報告 PDF 轉成的圖&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;1.2.840.10008.5.1.4.1.1.9.1.1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;12-Lead ECG Waveform&lt;/td&gt;&lt;td&gt;沒有任何像素，只有一連串電壓取樣值&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;12-Lead ECG Waveform 的 &lt;code&gt;PixelData&lt;/code&gt; 是空的，內容躺在 &lt;code&gt;WaveformSequence&lt;/code&gt; 裡：12 個導程，每個導程數千個 16-bit 整數。&lt;/p&gt;
&lt;p&gt;「12 導程」是哪 12 個、為什麼身上只接 10 條線，見本系列的&lt;a href=&quot;https://bobochen.dev/blog/ecg-why-10-wires-12-leads&quot;&gt;為什麼心電圖要用 10 條導線？&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;大部分 DICOM viewer 是為影像設計的（CT、MR、X 光）。丟一個沒有像素的檔案給它，得到空白畫面是預期行為，不是缺陷。&lt;/p&gt;
&lt;h2 id=&quot;weasis&quot;&gt;Weasis&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/nroduit/Weasis&quot;&gt;Weasis&lt;/a&gt; 把波形當一等公民，作者 Nicolas Roduit，EPL 2.0 / Apache 2.0 雙授權。&lt;/p&gt;
&lt;p&gt;確認它有獨立的波形模組，而不只是宣稱支援 —— 列出 OSGi bundle：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;for j in $(find ~/.weasis/cache-*/ -name &quot;bundle.jar&quot;); do
  unzip -p &quot;$j&quot; META-INF/MANIFEST.MF | awk &apos;/^Bundle-SymbolicName:/{print $2}&apos;
done | sort -u | grep dicom
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;weasis-dicom-codec
weasis-dicom-explorer
weasis-dicom-rt
weasis-dicom-sr
weasis-dicom-viewer2d
weasis-dicom-viewer3d
weasis-dicom-wave      ← 波形模組
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;weasis-dicom-wave&lt;/code&gt; 是獨立 bundle，不是塞進影像檢視器的附加功能。&lt;/p&gt;
&lt;h2 id=&quot;3-分鐘快速上手&quot;&gt;3 分鐘快速上手&lt;/h2&gt;
&lt;h3 id=&quot;裝與開&quot;&gt;裝與開&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install --cask weasis
open -a Weasis your-ecg.dcm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以把檔案拖進視窗，或用 File → Open。&lt;/p&gt;
&lt;h3 id=&quot;畫面長這樣&quot;&gt;畫面長這樣&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;以下是 v4.7.2 實際畫面上的元件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;左側 DICOM Explorer&lt;/strong&gt; — 載入的資料照 Patient / Study / Series 階層列出，縮圖標示為 &lt;code&gt;ECG&lt;/code&gt;。上方有 Search tags 可搜欄位。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;中央波形區&lt;/strong&gt; — 截圖的版面是 4 欄 × 3 列（I/aVR/V1/V4、II/aVL/V2/V5、III/aVF/V3/V6），最下方一條 II 導程的 rhythm strip。游標移到某導程時，上方會顯示該導程的 Minimum / Maximum 電壓。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;右側 Measurements 面板&lt;/strong&gt; — 分成 &lt;strong&gt;Markers&lt;/strong&gt;（游標量測結果）與 &lt;strong&gt;Annotations&lt;/strong&gt;（Tag / Value 表格）兩區。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;頂部三個下拉&quot;&gt;頂部三個下拉&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;控制項&lt;/th&gt;&lt;th&gt;畫面上的值&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Zoom&lt;/code&gt; 第一個下拉&lt;/td&gt;&lt;td&gt;&lt;code&gt;auto mm/s&lt;/code&gt;&lt;/td&gt;&lt;td&gt;X 軸走紙速度&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Zoom&lt;/code&gt; 第二個下拉&lt;/td&gt;&lt;td&gt;&lt;code&gt;auto mm/mV&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Y 軸增益&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Display format&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;4x2.5 Seconds with rhythm&lt;/code&gt;&lt;/td&gt;&lt;td&gt;導程版面配置&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;4x2.5 Seconds with rhythm&lt;/code&gt; 指 4 欄、每欄 2.5 秒，外加一條 rhythm strip。這和本系列第一篇談的 &lt;a href=&quot;https://bobochen.dev/blog/ecg-report-6x2-format&quot;&gt;6x2 格式&lt;/a&gt;是不同的版面慣例，下拉裡可以切換。&lt;/p&gt;
&lt;p&gt;另外還有一個獨立的 &lt;code&gt;Zoom: 100%&lt;/code&gt; 滑桿，等比縮放不改變上面兩個刻度。&lt;/p&gt;
&lt;h3 id=&quot;annotations-面板裡有什麼&quot;&gt;Annotations 面板裡有什麼&lt;/h3&gt;
&lt;p&gt;這是這次真正要看的地方。表格內容直接來自 DICOM 的 &lt;code&gt;WaveformAnnotationSequence&lt;/code&gt;：&lt;/p&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Tag&lt;/th&gt;&lt;th&gt;Value&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Filter Low Frequency&lt;/td&gt;&lt;td&gt;0.05 Hz&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Filter High Frequency&lt;/td&gt;&lt;td&gt;150.0 Hz&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Notch Filter Frequency&lt;/td&gt;&lt;td&gt;60.0 Hz&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Text&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Heart rate (&amp;#x3C;60 bpm)…&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ventricular Heart Rate&lt;/td&gt;&lt;td&gt;62.0 {H.B.}/min&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;PR Interval&lt;/td&gt;&lt;td&gt;167.0 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;QRS Duration&lt;/td&gt;&lt;td&gt;127.0 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;QT Interval&lt;/td&gt;&lt;td&gt;418.0 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;QTc Interval&lt;/td&gt;&lt;td&gt;428.0 ms&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;P Axis / QRS Axis / T Axis&lt;/td&gt;&lt;td&gt;56.0 / 74.0 / 44.0 deg&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;code&gt;Text&lt;/code&gt; 那列就是 &lt;code&gt;(0070,0006) UnformattedTextValue&lt;/code&gt; —— 判讀文字。其餘幾列則是各自帶 &lt;code&gt;ConceptNameCodeSequence&lt;/code&gt; 的數值型 annotation。&lt;/p&gt;
&lt;h2 id=&quot;什麼時候用-gui什麼時候用程式&quot;&gt;什麼時候用 GUI，什麼時候用程式&lt;/h2&gt;
&lt;p&gt;這次的任務是「確認一段文字精確等於什麼」。GUI 做得到，但表格會截斷（截圖裡就顯示成 &lt;code&gt;Heart rate (&amp;#x3C;60 bpm)Q…&lt;/code&gt;），而且看不出不可見字元。&lt;/p&gt;
&lt;p&gt;三行 Python 更適合：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;
ds = pydicom.dcmread(&quot;your-ecg.dcm&quot;)
for item in ds.WaveformAnnotationSequence:
    if &quot;UnformattedTextValue&quot; in item:
        print(repr(item.UnformattedTextValue))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用 &lt;code&gt;repr()&lt;/code&gt; 而不是 &lt;code&gt;print()&lt;/code&gt;。這次要抓的是文字裡混進 HTML 跳脫字元（&lt;code&gt;&amp;#x26;lt;&lt;/code&gt; 而不是 &lt;code&gt;&amp;#x3C;&lt;/code&gt;），這種差異在 GUI 上、甚至在一般 print 輸出裡都容易看漏，&lt;code&gt;repr()&lt;/code&gt; 會連跳脫字元和 &lt;code&gt;\r\n&lt;/code&gt; 一起攤開。&lt;/p&gt;
&lt;p&gt;適用場景：&lt;/p&gt;





















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;要驗什麼&lt;/th&gt;&lt;th&gt;用什麼&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;波形長相、雜訊、間期量測&lt;/td&gt;&lt;td&gt;Weasis&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;某欄位的值精確是什麼&lt;/td&gt;&lt;td&gt;pydicom + &lt;code&gt;repr()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;檔案格式合不合規（SOP Class、必填欄位）&lt;/td&gt;&lt;td&gt;pydicom&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;兩者驗的東西不同，不能互相取代。&lt;/p&gt;
&lt;h2 id=&quot;截圖前的去識別化&quot;&gt;截圖前的去識別化&lt;/h2&gt;
&lt;p&gt;要把畫面放進文件或簡報前，先把病患欄位換掉：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;ds = pydicom.dcmread(src)
ds.PatientName = &quot;DOE^JOHN&quot;
ds.PatientID = &quot;DEMO-0001&quot;
ds.save_as(dst)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Weasis 的 DICOM Explorer 和分頁標籤都會顯示 Patient ID，截圖時很容易連帶入鏡。&lt;/p&gt;
&lt;p&gt;另外，測試用的 &lt;code&gt;.dcm&lt;/code&gt; 如果放在 git repo 目錄下，記得確認 &lt;code&gt;.gitignore&lt;/code&gt; 有蓋到 —— &lt;code&gt;*.dcm&lt;/code&gt; 適合預設 ignore。&lt;/p&gt;
&lt;h2 id=&quot;重點整理&quot;&gt;重點整理&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.dcm&lt;/code&gt; 副檔名不代表同一種檔案，先看 SOP Class UID&lt;/li&gt;
&lt;li&gt;影像用的 viewer 打不開波形，是設計範圍問題不是缺陷&lt;/li&gt;
&lt;li&gt;Weasis 有獨立的 &lt;code&gt;weasis-dicom-wave&lt;/code&gt; 模組，判讀文字顯示在 Measurements → Annotations 的 &lt;code&gt;Text&lt;/code&gt; 列&lt;/li&gt;
&lt;li&gt;檢視波形用 GUI，驗證欄位值用 pydicom&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.DA9h6s1i.webp" medium="image"/><category>ECG</category><category>心電圖</category><category>DICOM</category><category>Weasis</category><category>醫療設備</category><enclosure url="https://bobochen.dev/_astro/cover.DA9h6s1i.webp" length="0" type="image/png"/></item><item><title>高鐵 App 分票：一支手機只能領一張票，小孩沒手機怎麼辦？</title><link>https://bobochen.dev/blog/thsr-app-one-phone-one-ticket/</link><guid isPermaLink="true">https://bobochen.dev/blog/thsr-app-one-phone-one-ticket/</guid><description>訂三張票，爸媽用手機取票，小孩沒手機就領不到。我原本以為這是為了方便列車長查票，查完官方原文才發現不是——卡點在「取票」那個瞬間，而且高鐵車上根本不逐一驗票。最後救我的是櫃檯站務員，但他請我下次不要再這樣：這句話比所有規則原文都值得討論。</description><pubDate>Mon, 03 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;訂三張高鐵票，我、太太、小孩。&lt;/p&gt;
&lt;p&gt;付完款，App 跳出「立即取票」，我點下去。手機領走了第一張。接著要把剩下兩張分給家人，太太的手機收到連結、輸入驗證碼，順利拿到第二張。&lt;/p&gt;
&lt;p&gt;然後我卡住了。&lt;/p&gt;
&lt;p&gt;第三張是小孩的票。小孩沒有手機。&lt;/p&gt;
&lt;p&gt;我當下的想法很單純：那這張去售票機領實體票就好了吧。結果不行。我已經在手機上取過票了，整筆訂位被鎖在「只能用 App 取」的狀態裡，而我家沒有第三支手機。&lt;/p&gt;
&lt;p&gt;（後來是怎麼解決的？我到車站櫃檯請服務人員幫忙印出實體票。這件事有個轉折，我放在文章後面講——它其實是整篇最值得看的地方。）&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;就是這張。我到櫃檯請站務印出來的實體票，右上角寫著「其他補 單程票」。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;那時候我心裡冒出一個解釋：這大概是為了方便列車長查票吧，一個人一張票，要看的時候直接把手機拿出來，很合理。&lt;/p&gt;
&lt;p&gt;這個解釋是錯的。我後來把官方文件、App 內的規則原文、以及台鐵的對照案例全部查了一遍，發現真正的卡點在完全不同的地方，而且高鐵自己從來沒有講過查票這個理由。&lt;/p&gt;
&lt;p&gt;這篇就是那趟查證的過程。&lt;/p&gt;
&lt;h2 id=&quot;先講結論&quot;&gt;先講結論&lt;/h2&gt;
&lt;p&gt;三句話：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;「一支手機只能領一張票」不是傳言，是 App 明文寫的，而且用詞是「須立即領取」——這是強制，不是上限。&lt;/li&gt;
&lt;li&gt;卡點在&lt;strong&gt;取票&lt;/strong&gt;那個瞬間，不在閘門。取票通路的決定掛在&lt;strong&gt;整筆訂位&lt;/strong&gt;上，不是掛在每一張票上；而且一旦選了手機取票，所有實體通路同時關閉。&lt;/li&gt;
&lt;li&gt;App 上寫的、給「同行者沒有智慧型手機」的解法，是&lt;strong&gt;退票重買&lt;/strong&gt;。而現場實務上站務人員可以幫你印實體票——但那是&lt;strong&gt;沒有寫在任何地方的例外&lt;/strong&gt;，而且他們會請你不要再這樣。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第三點我留到後面講。&lt;/p&gt;
&lt;h2 id=&quot;三張官方截圖&quot;&gt;三張官方截圖&lt;/h2&gt;
&lt;p&gt;以下截圖來自台灣高鐵官網的 T-EX App 操作說明頁，是高鐵自己放的示範圖，不是網友截圖。引用目的是討論這個產品設計，來源都附在圖說裡。&lt;/p&gt;
&lt;p&gt;有意思的是，官方拿來當示範的那筆訂單，票數是「全票 2、孩童 1、敬老 1」——正好就是「爸媽帶小孩、還有長輩」的組合。&lt;/p&gt;
&lt;h3 id=&quot;第一張取票通路是整筆訂位的二選一&quot;&gt;第一張：取票通路是整筆訂位的二選一&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;來源：&lt;a href=&quot;https://www.thsrc.com.tw/t/T-EX&quot;&gt;台灣高鐵 T-EX 行動購票 App 操作說明&lt;/a&gt;（官方示範圖，2026-08-03 擷取）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;畫面上寫著「請選擇取票方式」，底下兩個選項：&lt;code&gt;本手機取票及分票&lt;/code&gt;、&lt;code&gt;便利商店取票&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;這裡要先講清楚一件事，免得誤導：&lt;strong&gt;這兩個不是全部的取票通路。&lt;/strong&gt; 只要訂位還沒取票，你其實也可以直接走到車站，用自動售票機或窗口把票領出來。官方寫得很明白：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;已於網路訂票系統或 T-EX 行動購票 App 完成預訂者，可使用自動售票機付款及取票。&lt;/p&gt;
&lt;p&gt;—— &lt;a href=&quot;https://www.thsrc.com.tw/ArticleContent/6be4173c-91dc-4d06-ae3e-a19de420a237&quot;&gt;自動售票機購票&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;透過 App 預訂對號座車票，後續如欲透過本公司合作之便利商店或高鐵車站取票，可記下訂位代號並出示訂位時取票識別碼欄位使用之身分證明文件，即可親赴現場取票。&lt;/p&gt;
&lt;p&gt;—— &lt;a href=&quot;https://www.thsrc.com.tw/ArticleContent/4d262503-84bc-4963-a58e-4ca1a6453ad3&quot;&gt;T-EX 購票說明&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以問題不在「選項太少」。&lt;strong&gt;問題在於，不管你走哪一條通路，這個決定的單位都是「一整筆訂位」，而不是「一張票」。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;App 裡沒有「這兩張手機取、那一張去售票機領」的選項；走到車站也不是去挑其中一張來印。真正稀缺的東西不是通路，是&lt;strong&gt;粒度&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（附帶說明：能不能在售票機只領一整筆訂位中的部分票，官方頁面沒有明講，我沒有查到依據，所以這裡不下定論。）&lt;/p&gt;
&lt;h3 id=&quot;第二張選下去就鎖死&quot;&gt;第二張：選下去就鎖死&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;來源：&lt;a href=&quot;https://www.thsrc.com.tw/t/T-EX&quot;&gt;台灣高鐵 T-EX 行動購票 App 操作說明&lt;/a&gt;（官方示範圖，2026-08-03 擷取）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;點下「本手機取票及分票」之後，跳出這個確認框：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;非本人搭乘，請勿取/分票，並請將訂位代號與取票識別碼提供給欲乘車之旅客。&lt;/p&gt;
&lt;p&gt;於本手機取票後，即無法移轉至其他設備或通路取票，且限用本手機搭乘。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;「無法移轉至其他設備或&lt;strong&gt;通路&lt;/strong&gt;取票」這一句是整件事的樞紐。&lt;/p&gt;
&lt;p&gt;上一節說過，取票前你有超商、售票機、車站窗口這些實體通路可以走。但&lt;strong&gt;按下這個按鈕的那一刻，它們全部關閉&lt;/strong&gt;——換一支手機不行，改去超商、售票機、窗口領實體票也不行。&lt;/p&gt;
&lt;p&gt;官方購票說明頁講得更直白：「一旦執行分票後，該筆訂位紀錄內所有車票僅能透過 App 取票，無法選擇其他通路開票。」（&lt;a href=&quot;https://www.thsrc.com.tw/ArticleContent/4d262503-84bc-4963-a58e-4ca1a6453ad3&quot;&gt;購票說明&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;我按下那個按鈕的時候，並不知道我同時關掉了全家改領實體票的退路。&lt;/p&gt;
&lt;h3 id=&quot;第三張一機一票以及最狠的那句話&quot;&gt;第三張：一機一票，以及最狠的那句話&lt;/h3&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;來源：&lt;a href=&quot;https://www.thsrc.com.tw/t/T-EX&quot;&gt;台灣高鐵 T-EX 行動購票 App 操作說明&lt;/a&gt;（官方示範圖，2026-08-03 擷取）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這張圖有三件事值得看。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;上方的說明文字&lt;/strong&gt;寫著：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;您將使用「取票及分票」功能，本手機須立即領取其中一張(組)車票，請選擇欲下載至本手機的車票。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注意用詞是「須立即領取」，不是「僅可取得」。這代表它不只是個上限，而是強制你現在就拿走一張。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;中間的乘客清單&lt;/strong&gt;列著：乘客 1（成人）、乘客 2（成人）、乘客 3（孩童）、乘客 4（敬老），四個人平起平坐，每個人一個勾選框。&lt;/p&gt;
&lt;p&gt;系統裡沒有「孩童附掛在監護人票證下」這個概念。三歲小孩在這個資料模型裡，是一個需要自己的手機的獨立乘客。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下方的兩條警語&lt;/strong&gt;是我看完最有感覺的地方：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;※ 您即將進入分票功能，請務必確認同行旅客之手機皆已安裝本軟體。&lt;/p&gt;
&lt;p&gt;※ 執行本功能後，若同行者無智慧型手機或手機故障致無法取票時，限由原執行分票之手機辦理退票後，重新購買車票乘車。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;第二條就是官方對「小孩沒有手機」的正式解法：&lt;strong&gt;退票，重買&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這不是我推論出來的，是 App 自己寫的。而退票的代價是每張 20 元手續費、退款要 7 到 15 個工作天、原本的座位可能已經被別人買走。&lt;/p&gt;
&lt;p&gt;一個 UI 上的限制，就這樣被升級成金錢與時間的成本。&lt;/p&gt;
&lt;h3 id=&quot;把三張圖接起來看&quot;&gt;把三張圖接起來看&lt;/h3&gt;
&lt;p&gt;三張截圖分開看只是三個提示框，接起來才看得出結構：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/thsr-app-one-phone-one-ticket/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;問題全部集中在那個黃色的菱形。使用者在那裡要做一個決定，而這個決定的三個關鍵資訊都沒有出現在畫面上：它套用到整筆訂位、它不可逆、它會讓沒有手機的同行者無票可領。&lt;/p&gt;
&lt;p&gt;換個角度看同一件事——把取票通路畫成三扇門，會發現這是一扇單向門：&lt;/p&gt;
&lt;figure class=&quot;thsr-fig&quot;&gt;&lt;figcaption&gt;三條實體通路不是被關掉一條，是同時關掉。而且沒有回頭的箭頭——要回去只能退票重買。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;這張圖有兩個重點，第一個畫得出來：走進 App 那扇門之後，另外兩扇同時關上，而且沒有回頭的箭頭——要回去只能退票重買。&lt;/p&gt;
&lt;p&gt;第二個畫不出來，因為它是一條不存在的線：&lt;strong&gt;沒有「同一筆訂位，一部分走實體票、一部分走行動票證」這個選項。&lt;/strong&gt; 狀態掛在訂位上，不是掛在票上。這條缺線就是「無法部分分票」的全部原因。&lt;/p&gt;
&lt;h2 id=&quot;那我後來到底怎麼上車的&quot;&gt;那我後來到底怎麼上車的&lt;/h2&gt;
&lt;p&gt;前面那張圖的終點是「退票重買」。但我沒有退票。&lt;/p&gt;
&lt;p&gt;我到車站櫃檯，請服務人員幫忙。他把我那筆訂位的實體票印了出來，我們順利搭上車。&lt;/p&gt;
&lt;p&gt;不過他一邊處理，一邊很明確地跟我說：&lt;strong&gt;下次不可以這樣。&lt;/strong&gt; 如果小孩沒有手機，請我一開始就整筆訂位全部改成實體票——&lt;strong&gt;不支援部分數位取票&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我想了很久，覺得這段對話比前面所有的規則原文都重要。它一次講出了三件事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一，那個「唯一解法」其實不是唯一的。&lt;/strong&gt; 站務人員手上有工具可以救你。App 上寫的退票重買，是&lt;strong&gt;寫給你看的規則&lt;/strong&gt;，不是現場實際會發生的事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二，這個救援不在任何官方頁面上。&lt;/strong&gt; 我把高鐵官網相關頁面翻過一遍，找不到「已手機取票後可否請站務改印實體票」的程序說明。也就是說，它是一個&lt;strong&gt;存在但不被承認&lt;/strong&gt;的例外——你沒辦法事先知道它存在，因此沒辦法把它算進你的行程規劃裡。你只能到了現場，賭遇到願意幫忙的人、賭那個時段櫃檯不忙。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三，也是最傷的一點：他給我的建議，是叫我下次不要用這個功能。&lt;/strong&gt; 「小孩沒手機就全部改實體票」翻譯成產品語言就是——一個數位票證產品，對帶小孩的家庭給出的最佳實務是&lt;strong&gt;整組退出數位票證&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;從營運端的角度，這個建議完全合理：它避免了現場例外處理、避免了爭議、也避免了排隊。站務人員沒有做錯任何事，他只是在既有規則下給了最省事的答案。&lt;/p&gt;
&lt;p&gt;但這正是我覺得這件事值得寫出來的原因。&lt;strong&gt;當一個系統的正式規則會逼出例外處理，而例外處理又只能靠現場人員的善意跟時間來補，那個成本並沒有消失，它只是從產品轉嫁到了第一線員工跟旅客身上。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;順帶一提，他那句「不支援部分數位取票」，是我在整趟查證裡拿到最直接的一句話——它從營運方自己口中，確認了前面那張狀態圖上缺的那條線。&lt;/p&gt;
&lt;h2 id=&quot;在他們改之前你可以怎麼做&quot;&gt;在他們改之前，你可以怎麼做&lt;/h2&gt;
&lt;p&gt;上面講的是我怎麼踩到坑的。這一節是我查完之後整理給自己用的操作版——如果你只是想平安上車，看到這裡就夠了，後面是拆解。&lt;/p&gt;
&lt;h3 id=&quot;真正要做的決定只有一個而且在你點下取票之前&quot;&gt;真正要做的決定只有一個，而且在你點下取票之前&lt;/h3&gt;
&lt;p&gt;要想的不是「分票怎麼按」，是「這筆訂位到底該不該走手機取票」。這個決定一旦做完就鎖住整筆訂位，所以它必須在點下那個按鈕之前想清楚。&lt;/p&gt;
&lt;figure class=&quot;thsr-fig&quot;&gt;&lt;figcaption&gt;取票之前先數手機，不是先數人。只要少一支，整筆就走實體票。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;一句話版：只要同行的人裡有一個沒有智慧型手機，這筆訂位就整筆走實體票。&lt;/p&gt;
&lt;p&gt;這句話不是我發明的，是櫃檯站務員叫我下次照做的那句。&lt;/p&gt;
&lt;h3 id=&quot;決定走手機取票之後分票實際上長這樣&quot;&gt;決定走手機取票之後，分票實際上長這樣&lt;/h3&gt;
&lt;figure class=&quot;thsr-fig&quot;&gt;&lt;figcaption&gt;訂 3 張票：你的手機領走一張，另外兩張各自帶著自己的連結與驗證碼，分到別人手機上。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;順序是固定的五步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在乘客清單裡勾一個人，這張票立刻進你的手機——這一步沒得跳過。&lt;/li&gt;
&lt;li&gt;剩下的票留在訂位裡，一次選一張分出去。&lt;/li&gt;
&lt;li&gt;App 產生一條連結和一組取票驗證碼。&lt;/li&gt;
&lt;li&gt;用 LINE、簡訊或 Email 把那條連結傳給那個人。&lt;/li&gt;
&lt;li&gt;他點連結、開 T-EX、輸入驗證碼，票才落到他手機上。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;三件事要留意：傳出去的是連結，不是截圖（截圖進不了閘門）；對方手機必須先裝好同一個 App，連結才打得開；還有最重要的——這一切都建立在「每個人都有一支手機」的前提上，而那個前提在你按下第一步的時候就已經無法反悔了。&lt;/p&gt;
&lt;h3 id=&quot;七種最常撞上的狀況&quot;&gt;七種最常撞上的狀況&lt;/h3&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;狀況&lt;/th&gt;&lt;th&gt;怎麼辦&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;帶著還沒有手機的小孩&lt;/td&gt;&lt;td&gt;別碰手機取票。整筆改走便利商店、自動售票機或車站窗口，全家拿實體票。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;長輩沒有智慧型手機，或不會操作 App&lt;/td&gt;&lt;td&gt;同上。真要分票，出發前當面幫他把 App 裝好、驗證碼輸完再出門。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;已經手機取票了，才發現有人沒手機&lt;/td&gt;&lt;td&gt;實體通路在上一步就關掉了。App 寫的解法是退票重買。到現場拜託站務印實體票，實務上救得回來，但那不是官方程序，不要把它算進行程規劃。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;同行者手機裡沒有 T-EX&lt;/td&gt;&lt;td&gt;分票連結會請他先安裝。出門前在家庭群組問一句「App 裝了沒」，比在閘門前面教學快得多。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;連結傳錯人，或對方一直沒領&lt;/td&gt;&lt;td&gt;官方明文只寫到：同行者尚未取票時，退票限由執行分票的那支手機辦理。至於能不能收回重分，官方頁面沒有明講，我沒查到依據——所以送出前先確認收件人。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;買的是自由座&lt;/td&gt;&lt;td&gt;App 的自由座每次交易限購 1 張，本來就沒有分票這回事，各買各的。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;手機沒電、故障或遺失&lt;/td&gt;&lt;td&gt;帶訂位代號和訂票時登記的身分證件到車站窗口。App 的警語本來就把「手機故障致無法取票」列在退票重買的情境裡。&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h3 id=&quot;數字小抄&quot;&gt;數字小抄&lt;/h3&gt;





































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;項目&lt;/th&gt;&lt;th&gt;數字&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;App 每筆交易最多可訂對號座&lt;/td&gt;&lt;td&gt;10 張&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;去程、回程各自的上限&lt;/td&gt;&lt;td&gt;各 5 張&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;App 每筆交易可買的自由座&lt;/td&gt;&lt;td&gt;1 張&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;選「取票及分票」時本手機須立即領走&lt;/td&gt;&lt;td&gt;1 張&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;退票最晚辦理時間&lt;/td&gt;&lt;td&gt;發車前 30 分鐘&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;退票手續費&lt;/td&gt;&lt;td&gt;每張 20 元&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;退款入帳&lt;/td&gt;&lt;td&gt;7 到 15 個工作天&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;以上規則以&lt;a href=&quot;https://www.thsrc.com.tw/ArticleContent/4d262503-84bc-4963-a58e-4ca1a6453ad3&quot;&gt;台灣高鐵官網&lt;/a&gt;公告為準。&lt;/p&gt;
&lt;h2 id=&quot;所以是為了方便列車長查票嗎&quot;&gt;所以「是為了方便列車長查票」嗎&lt;/h2&gt;
&lt;p&gt;這是我一開始的假設。查完之後我認為：&lt;strong&gt;方向對了一半，但歸錯對象。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;先講支持這個假設的證據。台鐵的公文裡確實有「以一人一票原則配合查驗」這種寫法，把查驗跟一人一票連在一起。高鐵的旅客運送契約第五條也要求旅客提交車票正本，明文排除影印件與電子圖檔，包含手機票證的截圖。&lt;/p&gt;
&lt;p&gt;但這些條文規範的是「什麼東西算一張有效的票」，不是「一支手機能放幾張票」。這是兩個不同的問題。&lt;/p&gt;
&lt;p&gt;再看反方的證據，強度高很多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;高鐵車上根本不逐一驗票。&lt;/strong&gt; 列車長用的是平板上的「史密斯系統」（Seat Map Information System），輸入車次後以座位圖顯示各車廂使用狀況，座位用顏色區分票種、數字標示乘車區間，靠這個鎖定可疑座位再去查。高鐵公關專員顏喬偉受訪時也明確說過「高鐵的列車座椅並未設置感知器」（&lt;a href=&quot;https://www.mygopen.com/2023/03/taiwan-high-speed-rail.html&quot;&gt;MyGoPen 查核報導&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;既然不是逐人查票，「一人一張票比較好顯示」的效益本來就很小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;高鐵自己從來沒提過查票這個理由。&lt;/strong&gt; 我把官網相關頁面翻過一遍，找不到任何理由陳述，更沒有提到列車長。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限制出現的位置也不對。&lt;/strong&gt; 「一張（組）」這個限制寫在&lt;strong&gt;取票&lt;/strong&gt;畫面、FAQ 的取票段落、以及分票確認框裡，全部集中在取票與帳務階段，而不是乘車查驗的條款裡。如果動機是查票，規則不會長在這個位置。&lt;/p&gt;
&lt;figure class=&quot;thsr-fig&quot;&gt;&lt;figcaption&gt;規則長在哪裡，就透露它想解決什麼。三條全部落在取票，閘門和車上一條都沒有。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;還有一個最直接的反證：一支手機本來就可以放多張票。&lt;/strong&gt; 不同訂單的多張票可以共存在同一支手機上。如果真的是為了查票時一次只顯示一張，這件事應該一併被禁止才對。&lt;/p&gt;
&lt;p&gt;所以我的判斷是：查票便利頂多是&lt;strong&gt;附帶效果&lt;/strong&gt;，不是設計動機。把它當成主因，會讓後續的改善提案打錯靶。&lt;/p&gt;
&lt;h2 id=&quot;那真正的理由是什麼&quot;&gt;那真正的理由是什麼&lt;/h2&gt;
&lt;p&gt;高鐵沒有公開說過。但同樣是「一機一票」，台鐵在 2024 年被媒體追問時給過理由，而高鐵的契約條文也透露了同一套邏輯。&lt;/p&gt;
&lt;p&gt;台鐵當時的說法是兩點：避免旅客在閘門分次掃描 QR Code 影響其他人進出的順暢度，以及退換票必須在同一支手機操作的不便（&lt;a href=&quot;https://www.ctee.com.tw/news/20240706700552-431401&quot;&gt;工商時報 2024-07-06&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;高鐵這邊，從契約與說明頁可以推導出三條（&lt;strong&gt;以下是我的解讀，不是高鐵的官方說法&lt;/strong&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;票證必須是「活的正本」。&lt;/strong&gt; 運送契約要求提交車票正本，不得以影印件或電子圖檔代替，包含手機票證截圖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手機票證是不記名票。&lt;/strong&gt; 購票說明白紙黑字寫「手機票證為不記名乘車票」。不記名票一旦可以複製轉傳，等同可無限增生的有價證券。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;退票責任要能單一歸屬。&lt;/strong&gt; 一票一機，退票時永遠不會有兩台裝置爭執誰該退。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這三條加起來是有道理的。防止票被截圖轉傳、確保退款責任清楚，都是真實的營運需求。我不打算假裝這些考量不存在。&lt;/p&gt;
&lt;p&gt;但接下來這件事，讓「閘門順暢度」這個理由變得很難站得住腳。&lt;/p&gt;
&lt;h2 id=&quot;台鐵用九個多月推翻了自己&quot;&gt;台鐵用九個多月推翻了自己&lt;/h2&gt;
&lt;p&gt;時間軸是這樣的：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2024 年 7 月&lt;/strong&gt;，台鐵 e訂通 因為一對母子無法分票、被要求補票上了新聞。台鐵的回應就是前面那套：閘門順暢度、退換票不便。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2025 年 4 月 17 日&lt;/strong&gt;，台鐵上線「一機多票」，官方定義是「開放旅客在同一部手機上儲存多張行動票證服務」，新聞稿裡明確點名親子同行、長輩與孩童混合購票、輪椅旅客與陪同者這些情境（&lt;a href=&quot;https://www.cna.com.tw/news/ahel/202504170042.aspx&quot;&gt;中央社&lt;/a&gt;、&lt;a href=&quot;https://www.moj.gov.tw/2204/2473/2492/238921/&quot;&gt;法務部轉載新聞稿&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2026 年 6 月&lt;/strong&gt;，台鐵再升級：同一筆訂單的車票自動群組收納，進站時在 App 內左右滑動就能切換不同乘客的乘車條碼（&lt;a href=&quot;https://news.nextapple.com/life/20260624/2CF2DC3BE47F08BBFD020DD5ACCBC686&quot;&gt;壹蘋新聞&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/thsr-app-one-phone-one-ticket/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;九個多月。從「這樣會影響閘門順暢度」到「上線了」，中間隔了九個多月。&lt;/p&gt;
&lt;p&gt;這件事的意義不是「台鐵比較好」，而是它幫我們把問題分類了：&lt;strong&gt;閘門順暢度是軟約束，不是硬約束。&lt;/strong&gt; 它是可以用工程手段解決的，而且同業已經解過一次給大家看了。&lt;/p&gt;
&lt;p&gt;高鐵到 2026 年 8 月為止，仍然是一機一票。&lt;/p&gt;
&lt;h2 id=&quot;國際上怎麼做&quot;&gt;國際上怎麼做&lt;/h2&gt;
&lt;p&gt;我順手對標了十幾個系統，做法大致分成幾種典範：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一張票涵蓋全家&lt;/strong&gt;：美國 Amtrak 的 eTicket 是一張條碼涵蓋同一訂位的多名乘客，車掌用手持裝置掃一次就完成全體查驗。（附註：Amtrak 官網該頁我實測連不上，這條我沒能逐字驗證，引用前請自行查證。）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一機多票、逐張掃&lt;/strong&gt;：德國 DB Navigator、英國 Trainline 都允許一支手機持全家的票。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;身分即票證&lt;/strong&gt;：中國 12306 直接刷身分證進站，票面本身不是必要的。兒童票綁在成人證件下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不設閘門、靠車上查驗&lt;/strong&gt;：部分歐洲系統根本沒有進站閘門。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;台灣高鐵目前的做法——&lt;strong&gt;每一位乘客都必須有自己的智慧型手機&lt;/strong&gt;——在這個光譜上是相當嚴格的一端。&lt;/p&gt;
&lt;figure class=&quot;thsr-fig&quot;&gt;&lt;figcaption&gt;＊ Amtrak 那條的官網頁面我實測連不上，未逐字驗證。其餘系統的共同點是：沒有一個要求每位乘客各自帶一支手機。&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;有一件事我覺得特別值得提：連 Ticketmaster 這種以防黃牛為天職的售票平台，都允許一支手機持多張票，入場時掃一張、往旁邊滑、再掃下一張。它只是&lt;strong&gt;建議&lt;/strong&gt;你事先轉票以加快入場速度，而不是禁止。&lt;/p&gt;
&lt;p&gt;如果連演唱會票都能接受「一機多票、逐張掃」，那麼「怕影響閘門順暢」這個理由，需要拿出比直覺更強的證據。&lt;/p&gt;
&lt;h2 id=&quot;如果我是高鐵-pm-會怎麼改&quot;&gt;如果我是高鐵 PM 會怎麼改&lt;/h2&gt;
&lt;p&gt;先講一件事：我不會把「一機多票」排第一順位。&lt;/p&gt;
&lt;p&gt;這聽起來違反直覺，因為一機多票是最直接的解法。但它動到的是閘門吞吐，而閘門吞吐的風險我&lt;strong&gt;量不到&lt;/strong&gt;——我沒有高鐵的閘門通過時間基準線，也沒有尖峰時段的排隊數據。沒有分母就要求營運端放行，那是談判不是產品。&lt;/p&gt;
&lt;p&gt;真正該先做的是這個：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;把「取票通路」的決策粒度，從整筆訂位下放到每一張票，並且讓這個決策可以反悔。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;拆成幾層：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第 0 層：先把話講清楚。&lt;/strong&gt; 這層不動後端，只改 UI 與文案。目前那個二選一對話框沒有告訴你「按下去之後，全家改領實體票的路就關了」。光是在按鈕前面加一句「此選擇將套用到本訂位的全部 3 張票，且無法變更」，就能擋掉一大批誤觸。再往前一步，取票前先問一句「同行的 3 位旅客，各自有智慧型手機嗎？」——這是取票精靈該做的事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第 1 層之一：部分分票。&lt;/strong&gt; 每一張票各自選擇取票通路，而且在發車前 30 分鐘之前都可以無成本改變主意。爸媽手機取票、小孩那張去售票機領實體票，這個組合應該要成立。&lt;/p&gt;
&lt;p&gt;這一層直接解掉我原本的問題，而且&lt;strong&gt;它的風險是工程風險，不是營運風險&lt;/strong&gt;——可以估、可以測、不需要跟閘門部門談判。&lt;/p&gt;
&lt;p&gt;同一個家庭，在這層之後的流程長這樣：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/thsr-app-one-phone-one-ticket/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;跟前面那張現況圖比，差別只有兩個：決策粒度從訂位下放到票，以及那個決定可以反悔。沒有動閘門、沒有動資料模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第 1 層之二：一機多票。&lt;/strong&gt; 同訂單的多張票留在同一支手機。這層再做，而且開案前應該先去量高鐵自己的閘門吞吐基準線。台鐵已經證明這條路走得通，但「別人做得到」不等於「我們的閘門也做得到」，這是該用數據回答的問題。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第 2 層：隨行人模型。&lt;/strong&gt; 小孩不再是一個需要自己手機的獨立乘客，而是附掛在監護人票證下的隨行人。這是資料模型的改動，不是 UI 改動，所以排在後面。&lt;/p&gt;
&lt;p&gt;排序的邏輯不是保守，是&lt;strong&gt;風險型態不同&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/thsr-app-one-phone-one-ticket/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;可估的先做完，不可估的先去量。&lt;/p&gt;
&lt;p&gt;我看過太多產品討論一開始就跳到「那我們做群組票吧」，然後卡在跨部門協調半年，期間使用者的問題一天都沒有少。先出貨那個不需要別人點頭的版本，是對使用者比較負責的做法。&lt;/p&gt;
&lt;h2 id=&quot;反過來想&quot;&gt;反過來想&lt;/h2&gt;
&lt;p&gt;寫到這裡我應該誠實補一句：如果高鐵決定什麼都不改，最有說服力的理由是什麼？&lt;/p&gt;
&lt;p&gt;大概是這個——&lt;strong&gt;手機票證是不記名票，而不記名票的複製轉傳風險是真實的&lt;/strong&gt;。放寬到一機多票之後，一支手機可以同時持有多張不記名票，轉傳給不同人的操作成本會降低。高鐵作為對號座為主的營運模式，票價又不低，黃牛與轉售的誘因確實存在。&lt;/p&gt;
&lt;p&gt;這個顧慮我認為是成立的。但它指向的解法應該是&lt;strong&gt;動態條碼、票證時效化、或記名制&lt;/strong&gt;這類針對轉售的措施，而不是「強迫每個三歲小孩都要有一支手機」。&lt;/p&gt;
&lt;p&gt;用一個跟目標無關的限制去達成目標，代價會落在最不該承擔的人身上。這次是帶小孩的家長跟沒有智慧型手機的長輩。&lt;/p&gt;
&lt;h2 id=&quot;最後&quot;&gt;最後&lt;/h2&gt;
&lt;p&gt;我對這件事其實沒有很生氣。高鐵的 App 這幾年進步不少，分票功能本身也是為了解決多人同行才做的。&lt;/p&gt;
&lt;p&gt;讓我覺得可惜的是&lt;strong&gt;那個瞬間&lt;/strong&gt;——付完錢、點下取票、跳出二選一對話框的那個瞬間。使用者在那裡完全不知道自己正在做一個不可逆的決定，而系統知道這筆訂單有幾張票、有幾張是孩童票，它有全部的資訊可以提醒你，但它沒有。&lt;/p&gt;
&lt;p&gt;好的產品設計不會讓人在資訊不足的情況下按下無法反悔的按鈕。&lt;/p&gt;
&lt;p&gt;這一點，其實不用等到閘門改造，也不用等資料模型重寫。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;本文的查證來源與已知限制&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;文中所有官方規則引用皆附連結，截圖取自高鐵官網公開頁面，用於產品設計討論之評論引用。台鐵時間軸的日期與功能描述經中央社、法務部轉載新聞稿與壹蘋新聞交叉查證。&lt;/p&gt;
&lt;p&gt;已知限制：（1）Amtrak「一張 eTicket 涵蓋多名乘客」的官方頁面實測無法連線，該條未逐字驗證；（2）我沒有高鐵的內部數據，文中關於閘門吞吐的討論都是外部推論；（3）「已手機取票後請站務改印實體票」是我本人的一次現場經驗，&lt;strong&gt;不是官方程序&lt;/strong&gt;，我在官方頁面找不到任何依據，本文也不建議你把它當成可靠的退路；（4）能否在售票機或窗口只領取一整筆訂位中的部分車票，官方未見明文，本文未下定論。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.BEm2dYYj.webp" medium="image"/><category>產品設計</category><category>使用者體驗</category><category>台灣高鐵</category><category>產品拆解</category><category>PM</category><enclosure url="https://bobochen.dev/_astro/cover.BEm2dYYj.webp" length="0" type="image/png"/></item><item><title>企業 GenAI 平台負責人的三份工作：蓋平台、帶團隊、讓組織真的用起來</title><link>https://bobochen.dev/blog/enterprise-genai-platform-leadership/</link><guid isPermaLink="true">https://bobochen.dev/blog/enterprise-genai-platform-leadership/</guid><description>企業 GenAI 平台不是把聊天機器人接上內部資料，而是同時經營可重用能力、production 工程與組織 adoption。這篇從 RAG、Data Agent、自動化、roadmap 與 KPI，拆解平台負責人的三份工作。</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;如果每個部門都來問：「可以幫我們做一個 chatbot 嗎？」然後 AI 團隊一個一個接需求、各自串模型、各自接資料、各自部署——你手上不是一個平台，是一條永遠清不完的客製化專案 backlog。&lt;/p&gt;
&lt;p&gt;第一個 bot 上線時，大家會很興奮；第五個 bot 出現時，問題就來了：身分驗證做了五次、文件切分策略有五套、prompt 散落在五個 repo、同一份敏感資料被複製到不同的向量庫。模型換版要改五次，資安規則更新也要追五次。原本想用 AI 省時間，最後只是養出一群更難維護的新系統。&lt;/p&gt;
&lt;p&gt;所以我認為，企業 GenAI 平台負責人的核心任務，不是「做出最聰明的 AI」，而是把反覆出現的 AI 需求，收斂成一套&lt;strong&gt;安全、可重用、能持續交付的內部產品&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這份工作表面上在做 AI，實際上同時做三件事：&lt;strong&gt;蓋平台、帶團隊、讓組織真的用起來。&lt;/strong&gt; 少了任何一件，平台都很容易停在 demo。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/enterprise-genai-platform-leadership/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;第一份工作蓋的不是功能是可重用的平台&quot;&gt;第一份工作：蓋的不是功能，是可重用的平台&lt;/h2&gt;
&lt;p&gt;企業裡的 GenAI 需求看起來五花八門，但多半可以收斂成三種能力。&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;能力層&lt;/th&gt;&lt;th&gt;使用者想做的事&lt;/th&gt;&lt;th&gt;平台真正要提供的東西&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Knowledge&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;從內部文件找到可信答案&lt;/td&gt;&lt;td&gt;Enterprise RAG、來源引用、權限感知檢索&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Insight&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;用自然語言查資料、找趨勢&lt;/td&gt;&lt;td&gt;Data Agent、語意層、結構化與非結構化資料存取&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Action&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;讓 AI 接手一段工作流程&lt;/td&gt;&lt;td&gt;可重用工具、workflow orchestration、human-in-the-loop&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;第一層是讓 AI &lt;strong&gt;知道公司的事&lt;/strong&gt;。這不是把 PDF 丟進向量資料庫就結束，而是從 ingestion、chunking、embedding、retrieval、reranking 一路做到來源引用。企業使用者真正需要的不是一句流暢答案，而是「我能不能知道它根據哪份文件、哪個版本說的」。我在 &lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-rag-architecture/&quot;&gt;RAG 架構實戰&lt;/a&gt;裡拆過整條 pipeline；到了企業場景，還得補上&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-permission-aware-retrieval/&quot;&gt;權限感知檢索&lt;/a&gt;，確保每個人只能搜到自己原本有資格看的內容。&lt;/p&gt;
&lt;p&gt;第二層是讓 AI &lt;strong&gt;看懂資料、產生洞察&lt;/strong&gt;。這比文件問答再往前一步：agent 可能同時查詢資料表、報表和文字紀錄，再把結果組成適合當下問題的圖表或摘要。真正困難的通常不是畫出圖，而是語意定義與權限——「營收」到底用哪張表、退貨算不算、這個人能不能看到部門以外的明細？這些沒有先講清楚，AI 只會用更自然的語言講出更難察覺的錯誤。&lt;/p&gt;
&lt;p&gt;第三層是讓 AI &lt;strong&gt;採取行動&lt;/strong&gt;。查到資料之後，可以建立工單、更新狀態、觸發審批或接續下一個流程。這時平台必須把工具分成唯讀與寫入、為高風險動作保留人工確認，並處理 timeout、retry、idempotency 和 rollback。Agent 會講錯話已經夠麻煩；當它能動手，錯誤就會真的改變外部世界。&lt;/p&gt;
&lt;p&gt;三層共用的底座，才是平台價值最大的地方：身分與權限、tool registry、模型路由、eval、observability、audit log、成本監控。每個團隊不必再重蓋一次，而且公司只要在一個地方強化安全規則。完整的元件關係，可以對照我整理的&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-system-architecture-blueprint/&quot;&gt;企業 AI Agent 系統架構藍圖&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;平台的價值不是「它自己有多少功能」，而是下一支團隊能不能少走一段已經走過的路。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;第二份工作把願景翻成-roadmap也翻成可以驗收的-kpi&quot;&gt;第二份工作：把願景翻成 roadmap，也翻成可以驗收的 KPI&lt;/h2&gt;
&lt;p&gt;「我們要做企業 AI 平台」不是 roadmap，只是一個方向。真正的 roadmap 必須回答三個問題：&lt;strong&gt;先解哪一段流程、為什麼先解它、做到什麼程度算成功。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我不會一開始就做一個號稱什麼都能接的通用框架。比較務實的順序是：先選一個價值明確、資料邊界清楚、失敗風險可控的工作流程，做出 reference workflow；等第二、第三個場景進來，再把真正重複的部分抽成共用能力。&lt;/p&gt;
&lt;p&gt;這和過早抽象化程式碼是一樣的道理。只看第一個 use case，你以為所有人都需要同一套設定；做到第三個才會發現，真正共用的可能只有 identity、retrieval、evaluation 和 audit，其他都該留給產品團隊自己決定。&lt;/p&gt;
&lt;p&gt;Roadmap 的另一半是 KPI。只看「做了幾個 agent」或「模型分數多高」都不夠，我會把指標分成四層：&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;面向&lt;/th&gt;&lt;th&gt;可以追的指標&lt;/th&gt;&lt;th&gt;它回答的問題&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Adoption&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;活躍團隊數、共用元件採用率、time-to-first-value&lt;/td&gt;&lt;td&gt;別人真的用得起來嗎？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Quality&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;grounded answer 通過率、task success rate、人工升級率&lt;/td&gt;&lt;td&gt;回答和行動值得信任嗎？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Engineering&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;availability、p95 latency、每次成功任務成本&lt;/td&gt;&lt;td&gt;能穩定、可負擔地運作嗎？&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Business&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;節省工時、減少人工步驟、縮短決策週期&lt;/td&gt;&lt;td&gt;對業務結果有幫助嗎？&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;每一個 KPI 都要有 baseline 和 owner。沒有導入前的處理時間，就不能證明導入後省了多少；沒有人負責品質指標，dashboard 最後只會變成一張沒人看的漂亮圖。&lt;/p&gt;
&lt;p&gt;平台負責人的技術判斷，也包含管理 technical debt。新模型、新框架每週都在出，但不能因此每季重寫一次平台。該穩定的是介面與工程紀律：身份怎麼傳、工具怎麼註冊、eval 怎麼過關、trace 怎麼留下。框架可以替換，這些 contract 不能跟著每次流行一起翻修。&lt;/p&gt;
&lt;h2 id=&quot;第三份工作帶的不是一群專家是一個-delivery-system&quot;&gt;第三份工作：帶的不是一群專家，是一個 delivery system&lt;/h2&gt;
&lt;p&gt;企業 GenAI 平台通常需要 software engineer 和 ML engineer 一起工作，但「把兩種人放進同一個 Slack channel」不等於跨領域團隊。&lt;/p&gt;
&lt;p&gt;一個 RAG 答錯，原因可能在文件 ingestion、chunking、metadata、檢索、reranking、prompt，也可能是原始資料本來就互相矛盾。Software engineer 只看 API 是否回 200，ML engineer 只看模型輸出是否流暢，都找不到完整答案。團隊需要的是共同的品質語言，以及一套每次都能重複的 delivery loop：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;需求與風險盤點 → 架構／安全 review → 建立黃金題庫 → 實作
→ eval 回歸 → 上線 → 觀測 → 把線上失敗案例補回題庫 → …
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;這個 loop 最重要的是最後那個箭頭。線上每出現一個壞案例，團隊不只修當下的答案，還要把它變成以後每次改 prompt、換模型、調 retrieval 都必須通過的測試。沒有這一步，同一類錯誤遲早換個樣子再回來。&lt;/p&gt;
&lt;p&gt;AI 工程也不只需要 code review。Architecture review 看權限與系統邊界，prompt review 要附 eval diff，eval review 檢查題庫是否真的代表使用情境。這些做法的目的不是增加流程，而是讓團隊不必靠某個資深成員的直覺守住品質。更完整的做法，我整理在&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-llm-observability-eval/&quot;&gt;生產級 LLM 可觀測性與評估&lt;/a&gt;和&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-leading-small-ai-team/&quot;&gt;帶領一支 AI 工程小隊&lt;/a&gt;兩篇裡。&lt;/p&gt;
&lt;p&gt;管理者在這裡的工作，是讓探索和交付可以同時存在：保留實驗空間，但也清楚分開 production backlog、平台能力與技術探索。不是每個成功的 prototype 都要進平台，也不是每個新框架都值得團隊付出遷移成本。&lt;/p&gt;
&lt;h2 id=&quot;真正決定成敗的是讓組織用起來&quot;&gt;真正決定成敗的，是讓組織用起來&lt;/h2&gt;
&lt;p&gt;平台做完卻沒人用，通常不是因為模型不夠強，而是使用門檻太高。&lt;/p&gt;
&lt;p&gt;如果一個產品團隊想做文件問答，得先研究向量資料庫、自己接 SSO、自己發明權限 metadata、自己裝 tracing，再來理解模型費用，那它很可能乾脆直接呼叫一個 API，先做出能 demo 的版本。三個月後，企業裡就會多一個沒人敢維護的孤島。&lt;/p&gt;
&lt;p&gt;所以平台團隊要提供 &lt;strong&gt;golden path&lt;/strong&gt;：一條預設安全、可以被複製、出事找得到紀錄的最短路徑。它不只是 SDK，還包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一個能跑起來的 reference implementation，而不是只有 API 文件。&lt;/li&gt;
&lt;li&gt;清楚的 tutorial，讓第一次接觸的人知道從哪裡開始。&lt;/li&gt;
&lt;li&gt;預設接好 identity、權限、audit、eval 與 cost tracking。&lt;/li&gt;
&lt;li&gt;有 owner 的支援管道，以及常見失敗模式的 troubleshooting。&lt;/li&gt;
&lt;li&gt;明確的 escape hatch；特殊需求可以離開 golden path，但要知道自己接手了哪些責任。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Developer experience 不是把文件網站做漂亮，而是縮短一支內部團隊從「有想法」到「安全上線」的距離。如果以前要三個月、現在兩週能完成，而且沒有重蓋權限、監控與評估，平台才真的產生 leverage。&lt;/p&gt;
&lt;p&gt;對 business stakeholder 的溝通也一樣。對方通常不在乎用了哪個 vector database 或 orchestration framework，他在乎的是：原本要兩天整理的資料現在多久完成、錯誤怎麼被攔下、每個月成本多少、出事時找不找得到原因。技術負責人的價值，就是把架構決策翻成這些能做決定的語言。&lt;/p&gt;
&lt;h2 id=&quot;如果從零開始我會這樣走前-90-天&quot;&gt;如果從零開始，我會這樣走前 90 天&lt;/h2&gt;
&lt;p&gt;這不是所有組織都適用的固定公式，但它能避免平台一開始就變成一場過度設計的大工程。&lt;/p&gt;
&lt;h3 id=&quot;day-130先畫問題與邊界不急著選框架&quot;&gt;Day 1–30：先畫問題與邊界，不急著選框架&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;盤點最常出現、最值得重用的知識、分析與流程需求。&lt;/li&gt;
&lt;li&gt;找出資料 owner、權限來源、風險等級與目前的人工 baseline。&lt;/li&gt;
&lt;li&gt;選一個低風險、高頻率、成果容易驗收的 reference workflow。&lt;/li&gt;
&lt;li&gt;定義第一版 KPI、non-goal 與需要人工確認的 action boundary。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;day-3160做出-reference-workflow也做出第一條-golden-path&quot;&gt;Day 31–60：做出 reference workflow，也做出第一條 golden path&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;端到端完成 ingestion、retrieval／tool use、eval、部署與觀測。&lt;/li&gt;
&lt;li&gt;不只測 happy path，也把權限錯誤、資料缺漏、模型 timeout 放進題庫。&lt;/li&gt;
&lt;li&gt;和第一批內部使用者一起使用，而不是交付後才等 feedback。&lt;/li&gt;
&lt;li&gt;記錄哪些元件真的被重複使用，哪些只是單一場景的產品邏輯。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;day-6190把重複的能力平台化&quot;&gt;Day 61–90：把重複的能力平台化&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;抽出穩定的 API、SDK、template 與 policy，不急著抽象所有差異。&lt;/li&gt;
&lt;li&gt;建立 onboarding 文件、支援管道、service ownership 與 incident 流程。&lt;/li&gt;
&lt;li&gt;上線 adoption、品質、可靠性、成本與 business impact dashboard。&lt;/li&gt;
&lt;li&gt;用真實使用資料排下一季 roadmap，而不是用最新技術新聞排。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;90 天之後最重要的產出，不是「完成了平台 1.0」，而是證明這個團隊找到了一個能反覆運轉的 loop：從問題出發、交付能力、量測結果，再把學到的東西餵回 roadmap。&lt;/p&gt;
&lt;h2 id=&quot;這條路為什麼吸引我&quot;&gt;這條路為什麼吸引我&lt;/h2&gt;
&lt;p&gt;回頭看自己的職涯，從第一位 Backend &amp;#x26; DevOps 工程師、把產品後端從零建起，到後來帶團隊、做技術總監，我最常做的其實都是同一件事：&lt;strong&gt;把一次性的解法，整理成別人也能使用、團隊能持續維護的系統。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在&lt;a href=&quot;https://bobochen.dev/blog/bobo-chen-career-map-chapter-07/&quot;&gt;從零建立醫療 SaaS 後端的那段經歷&lt;/a&gt;裡，難的不是選 PHP、Protocol Buffers 或 Kubernetes，而是把部署、監控、權限與開發流程一起建立起來；到了&lt;a href=&quot;https://bobochen.dev/blog/bobo-chen-career-map-chapter-09/&quot;&gt;技術總監階段&lt;/a&gt;，工作也從「我能不能解掉問題」，變成「團隊能不能看見問題、留下知識、持續交付」。&lt;/p&gt;
&lt;p&gt;企業 GenAI 平台把這些老問題換了一組新元件：向量檢索、agent runtime、model router、eval harness。但底層仍然是熟悉的系統工程、平台思維與技術領導。&lt;/p&gt;
&lt;p&gt;這也是我寫完整套「從 PoC 到 Production：企業 AI Agent 系統工程」系列後，越來越確定的一件事：GenAI 平台負責人真正要交付的，不是一個很會說話的 demo，而是三個結果——&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;業務拿到的是可信任的能力，不需要理解底下每個模型。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工程團隊拿到的是可重用的路徑，不必重蓋安全與維運底座。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;組織拿到的是持續交付的方法，不會因為工具改名就全部重來。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;能做到這三件事，才算真的把 GenAI 從一個專案，變成一個平台。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;延伸閱讀&quot;&gt;延伸閱讀&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-system-architecture-blueprint/&quot;&gt;企業 AI Agent 系統架構藍圖&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-rag-architecture/&quot;&gt;RAG 架構實戰：從文件 ingestion 到 source-cited 回答&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-permission-aware-retrieval/&quot;&gt;權限感知檢索：企業 RAG 最容易被略過的一關&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bobochen.dev/blog/enterprise-ai-agent-leading-small-ai-team/&quot;&gt;帶領一支 3–8 人的 AI 工程小隊&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.CobA6Mwh.webp" medium="image"/><category>GenAI Platform</category><category>Enterprise RAG</category><category>AI Agent</category><category>平台工程</category><category>技術領導</category><enclosure url="https://bobochen.dev/_astro/cover.CobA6Mwh.webp" length="0" type="image/png"/></item><item><title>每個網站都要你同意 cookie，但根本沒有法律規定要有這個視窗</title><link>https://bobochen.dev/blog/hackernews-kill-the-cookie-banner-2026-07-27/</link><guid isPermaLink="true">https://bobochen.dev/blog/hackernews-kill-the-cookie-banner-2026-07-27/</guid><description>你一定點過那種「隱私權同意」的按鈕——但沒有任何一條法律規定網站要放它。它是業者為了繼續追蹤你而發明出來的通行證。這篇用最白話的方式講清楚：它到底是誰弄出來的、為什麼十年下來變成一場騙你按同意的比賽、歐洲現在想怎麼收拾，以及台灣網站經營者最實際的一件事——你的網站可能根本不需要它。</description><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;這篇的起點是 &lt;a href=&quot;https://killthecookiebanner.eu/&quot;&gt;Kill The Cookie Banner&lt;/a&gt;——七個歐洲隱私團體（noyb、EDRi、EFF、BEUC 等）發起的連署行動。它在 Hacker News 衝到 1,148 分、591 則討論。我想趁這個機會，把這件每個人天天遇到、卻幾乎沒人搞懂的事，從頭講一次。不用懂法律，也不用會寫程式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你一定點過那種「隱私權同意」的按鈕。&lt;/p&gt;
&lt;p&gt;打開一個新聞網站，畫面一暗，跳出一塊擋板：「我們重視您的隱私。」下面兩顆按鈕，一顆藍色的「接受全部」很大很好按，一顆灰灰的「管理偏好設定」小小的躲在角落。你沒空理它，手指自動往藍色那顆按下去，然後才看得到你本來要看的東西。&lt;/p&gt;
&lt;p&gt;如果你平常很少逛歐洲網站，可能還沒被這樣擋過。它長這樣——這是倫敦交通局的官網，你只是想查一下公車幾點來，得先把這題答完才進得去：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;在歐洲，這是一天三五次、一年上千次的日常。而這件事，已經持續了十年。&lt;/p&gt;
&lt;p&gt;而我要說的是：&lt;strong&gt;沒有任何一條法律，規定網站必須放那個東西&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一條都沒有。&lt;/p&gt;
&lt;h2 id=&quot;先把最容易誤會的事講清楚法律管的是別偷翻客戶的包包而不是要放同意書&quot;&gt;先把最容易誤會的事講清楚：法律管的是「別偷翻客戶的包包」，而不是「要放同意書」&lt;/h2&gt;
&lt;p&gt;我用一個生活場景來講，這是整篇文章最重要的一段，看懂這段其他都好懂了。&lt;/p&gt;
&lt;p&gt;想像你走進一家店。法律規定：&lt;strong&gt;店員不能未經同意翻你的包包&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;好，現在店家有兩條路可以走：&lt;/p&gt;
&lt;p&gt;第一條路：那我就不翻了。客人進來，買東西，走人，皆大歡喜。&lt;/p&gt;
&lt;p&gt;第二條路：我還是想翻，所以我在門口擺一張桌子，每個客人進門都得先簽一份「我同意你翻我包包」的同意書，簽完才放行。&lt;/p&gt;
&lt;p&gt;法律從頭到尾只寫了一句「不能未經同意翻包包」。&lt;strong&gt;是店家自己選了第二條路&lt;/strong&gt;。門口那張桌子、那份同意書、那個排隊，全部是店家為了「繼續翻」而發明出來的東西，不是法律要求的。&lt;/p&gt;
&lt;p&gt;Cookie 同意視窗就是門口那張桌子。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;法律說的是：沒有你的同意，不准在你的裝置上偷偷存東西、讀東西。網站業者聽完，絕大多數選了第二條路——我還是要追蹤，所以我發明一個視窗來收集你的同意。&lt;/p&gt;
&lt;p&gt;Hacker News 上有個網站經營者的留言，把這件事講得很直接：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我是好幾家公司的 data controller，對 GDPR 很熟。我手上所有網站都沒有 cookie 同意視窗，因為它們根本不需要。我猜大部分公司只是懶得讀法條就先掛上去，不然就是他們真的在收集和販賣個資。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;這就是重點。那個視窗的存在，其實是在告訴你一件事：&lt;strong&gt;這個網站選了第二條路&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最好的證明是 Amazon。同一家公司、同一套系統，你打開 amazon.com 什麼都不會跳；打開 amazon.fr，整片同意書就蓋上來：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;同一家公司、同一批 cookie，差別只在訪客站在地球上的哪一格。這正好說明了：那個視窗從來不是技術限制，而是一個商業決定。&lt;/p&gt;
&lt;h2 id=&quot;那到底是哪條法律答案不是-gdpr是一條-2002-年的老規定&quot;&gt;那到底是哪條法律？答案不是 GDPR，是一條 2002 年的老規定&lt;/h2&gt;
&lt;p&gt;這是最多人搞錯的地方，連很多工程師都以為是 GDPR 規定的。&lt;/p&gt;
&lt;p&gt;不是。&lt;/p&gt;
&lt;p&gt;真正的源頭叫 &lt;strong&gt;ePrivacy Directive&lt;/strong&gt;，2002 年就有了，2009 年修訂過一次。它管的事情很單純：&lt;strong&gt;要在使用者的裝置上存資料、或讀取已經存在那裡的資料，得先經過同意&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;注意這句話裡面沒有「cookie」這個字。這很關鍵，等一下會回來講。&lt;/p&gt;
&lt;p&gt;時間軸大概是這樣：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2002 年&lt;/strong&gt;：ePrivacy Directive 上路，原本的規定比較鬆，網站只要告知、讓你可以拒絕就行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2009 年&lt;/strong&gt;：修訂版把它改成「要事前同意」。歐盟各國陸續在 2011 年前後把它寫進國內法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2011 年起&lt;/strong&gt;：歐洲網站開始冒出那種底部的小灰條，「本站使用 cookie，繼續瀏覽視為同意」。當時還沒那麼煩。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2018 年&lt;/strong&gt;：GDPR 上路。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GDPR 沒有發明 cookie 視窗。它做的事情是&lt;strong&gt;把「同意」這兩個字的定義訂嚴了&lt;/strong&gt;：同意必須是主動給的（不能預設打勾）、必須具體到每個用途（不能一句「我們使用 cookie」打包）、而且收回同意要跟給出同意一樣容易。&lt;/p&gt;
&lt;p&gt;於是原本那條「繼續瀏覽視為同意」的小灰條不合格了。它被迫長大、長出按鈕、長出分頁、長出一整排開關——變成今天擋在你面前的那塊板子。&lt;/p&gt;
&lt;p&gt;所以正確的說法是：&lt;strong&gt;規定同意的是 2002 年的 ePrivacy，把同意標準拉高、讓視窗變大的才是 GDPR&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&quot;有兩種情況本來就不用問&quot;&gt;有兩種情況本來就不用問&lt;/h3&gt;
&lt;p&gt;法律不是不通人情。有兩類情況免同意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;純粹為了把資料傳出去所必需的&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用者自己要求的服務所必需的&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第二類白話講就是：你按了「登入」，網站當然要記得你是誰，所以登入 session 的 cookie 免同意。你把東西丟進購物車，網站要記得，購物車 cookie 免同意。你選了繁體中文，記住這個偏好，免同意。防機器人的驗證，免同意。&lt;/p&gt;
&lt;p&gt;這些是「你要求的服務」的一部分。沒有它們，網站就壞了。&lt;/p&gt;
&lt;p&gt;不用問的是這些。要問的是&lt;strong&gt;追蹤&lt;/strong&gt;：你看了什麼、停留多久、從哪裡來、還去過哪些網站——這些跟「提供你要的服務」沒關係，是拿去做廣告和分析的。&lt;/p&gt;
&lt;h3 id=&quot;還有一個很多人沒注意到的陷阱&quot;&gt;還有一個很多人沒注意到的陷阱&lt;/h3&gt;
&lt;p&gt;前面我特別提過，法條裡沒有「cookie」這個字。它寫的是「在終端裝置上存取資訊」。&lt;/p&gt;
&lt;p&gt;這代表兩件事，兩件都很多人搞錯：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你就算不用 cookie，改用 localStorage、改用瀏覽器指紋、甚至只是讀取螢幕解析度——&lt;strong&gt;照樣可能算數&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;反過來，「我沒有用 cookie」也不是免死金牌。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;「有沒有 cookie」從來不是重點。&lt;strong&gt;重點是你有沒有伸手進別人的裝置&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;為什麼十年下來變成一場騙你按同意大賽&quot;&gt;為什麼十年下來，變成一場「騙你按同意」大賽&lt;/h2&gt;
&lt;p&gt;如果那個視窗是誠實的，其實也還好。問題是它不誠實，而且是集體的、有系統的不誠實。&lt;/p&gt;
&lt;p&gt;隱私組織 noyb 掃了上萬個歐洲網站，數字長這樣：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;45%&lt;/strong&gt; 的同意視窗預設幫你勾好「全部同意」。&lt;/li&gt;
&lt;li&gt;拒絕所需要的點擊次數，普遍是接受的&lt;strong&gt;兩倍&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;只有 &lt;strong&gt;2.18%&lt;/strong&gt; 的使用者會點進第二層設定頁面。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;然後是最刺眼的那組對照：業界自己的統計顯示，真心想被追蹤的人大約只有 &lt;strong&gt;3%&lt;/strong&gt;，但實際上按下同意的超過 &lt;strong&gt;90%&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;3% 想要，90% 答應了。&lt;/p&gt;
&lt;p&gt;這中間 87 個百分點的差距，不是因為大家忽然想通了，是&lt;strong&gt;設計出來的&lt;/strong&gt;。這在設計圈有個專有名詞叫「dark pattern」（暗黑模式），意思是刻意把介面設計成引導你做出違背自己意願的選擇。&lt;/p&gt;
&lt;p&gt;具體手法你一定都遇過：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接受是彩色大按鈕，拒絕是灰字小連結，或者根本沒有拒絕鍵，只有「管理設定」。&lt;/li&gt;
&lt;li&gt;點進「管理設定」，一整排開關等著你一個一個關。&lt;/li&gt;
&lt;li&gt;關到一半發現還有第二個分頁叫「正當利益」（legitimate interest），裡面 137 家你沒聽過的公司，&lt;strong&gt;預設全部是開的&lt;/strong&gt;，要再一個一個關。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;講抽象的不好懂，我實際點一次給你看。這是德國最大的科技媒體 heise.de：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;先看第一句：我們和最多 &lt;strong&gt;193 家合作夥伴&lt;/strong&gt;。再看按鈕：同意、付錢、設定。&lt;strong&gt;沒有拒絕鍵&lt;/strong&gt;。你想免費看、又不想被追蹤？這個介面沒打算給你這個選項。&lt;/p&gt;
&lt;p&gt;那就點「Settings」進第二層：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;兩個細節：第一，合作夥伴的數字從 193 變成了 205——同一個網站、隔一層畫面，數字就不一樣。第二，最想拒絕的那一項「廣告商的個人化廣告與剖析」&lt;strong&gt;只有同意鍵、沒有拒絕鍵&lt;/strong&gt;，旁邊寫著「免費使用需要此同意」。&lt;/p&gt;
&lt;p&gt;你點了兩層進來，只是為了發現那一項不能拒絕。&lt;/p&gt;
&lt;p&gt;而清單上最後那一招——「正當利益」——最陰險。它原本是法律裡的一個概念，意思是某些情況下不用取得同意也能處理資料。廣告業把它拿來當後門用：你以為你按了「全部拒絕」，其實只拒絕掉了「同意」那一欄，「正當利益」那一欄還開著。&lt;/p&gt;
&lt;p&gt;回到文章開頭那個倫敦交通局。你點「Manage cookies」進第二層，看到四個分類的勾選框，關一關以為結束了。但那一頁底下藏著一行小字連結，點下去才進得到第三層：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;看那句話：部分合作夥伴依據的是「正當利益」，而不是你的同意；你有權拒絕。&lt;/p&gt;
&lt;p&gt;有權，但你得先點進第二層、找到那行小字進第三層、再切分頁、再一格一格取消勾選。而你本來只是想查公車幾點來。&lt;/p&gt;
&lt;p&gt;Hacker News 上有人一句話總結：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「我們看到你不想被跟蹤了，但我們還是想，所以請針對每一家合作夥伴，再確認一次你不想。」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;而且這不只是道德問題。整個廣告業共用的那套同意框架叫 IAB TCF，2022 年被比利時資料保護機關認定違規、開罰；2024 年 3 月歐盟法院（CJEU）判定它產生的同意字串可能構成個人資料；2025 年 5 月布魯塞爾上訴法院確認這套框架不完全符合 GDPR。&lt;/p&gt;
&lt;p&gt;換句話說——&lt;strong&gt;這場十年大戲最大的反諷是：那些讓你煩到抓狂的 cookie 視窗，本身很多是違法的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它們不是為了保護你才存在的。它們是為了在「不能偷翻包包」的規定下，合法地繼續翻你的包包。&lt;/p&gt;
&lt;h2 id=&quot;kill-the-cookie-banner-想幹嘛把開關搬回瀏覽器&quot;&gt;Kill The Cookie Banner 想幹嘛：把開關搬回瀏覽器&lt;/h2&gt;
&lt;p&gt;講到這裡，這次在 Hacker News 上炸開的那個行動，訴求就很好懂了。&lt;/p&gt;
&lt;p&gt;它的邏輯是這樣：&lt;strong&gt;如果每個網站都要問你一次，那乾脆讓你回答一次就好&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這件事其實一點都不新奇，你的瀏覽器早就在做類似的事了。你設定過「我要看繁體中文」嗎？大部分人沒有——但你打開網站，它就是中文的。因為瀏覽器每次連線時都會自動附上一句「這位使用者偏好中文」。沒有人需要在每個網站的門口，重新選一次語言。&lt;/p&gt;
&lt;p&gt;那隱私為什麼不行？&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id=&quot;這招嘗試過了而且失敗過一次&quot;&gt;這招嘗試過了，而且失敗過一次&lt;/h3&gt;
&lt;p&gt;失敗的那次叫 &lt;strong&gt;Do Not Track（DNT）&lt;/strong&gt;，2009 年就有了。原理一模一樣：瀏覽器打一個開關，每次連線附上一句「我不想被追蹤」。&lt;/p&gt;
&lt;p&gt;結果呢？網站集體裝作沒看到。&lt;/p&gt;
&lt;p&gt;原因很簡單：&lt;strong&gt;它是純自願的，沒有任何法律逼人聽&lt;/strong&gt;。而且更荒謬的是——因為只有少數人會去開這個開關，「有開 DNT」這件事本身反而變成一個辨識你的特徵，讓你更好被追蹤。&lt;/p&gt;
&lt;p&gt;於是它死得很難看。Apple 在 2019 年的 Safari 12.1 把它拿掉，理由是「防止它被拿來當指紋辨識的參數」；Mozilla 在 2025 年 2 月的 Firefox 135 也移除了這個選項，理由是「很多網站不尊重它，某些情況下反而降低隱私」。&lt;/p&gt;
&lt;h3 id=&quot;現在這次不一樣的地方有法律當靠山&quot;&gt;現在這次不一樣的地方：有法律當靠山&lt;/h3&gt;
&lt;p&gt;接替 DNT 的東西叫 &lt;strong&gt;GPC（Global Privacy Control）&lt;/strong&gt;，技術上跟 DNT 沒什麼差別，差別在後面站著誰。&lt;/p&gt;
&lt;p&gt;美國那邊已經走在前面了：到 2026 年 1 月 1 日為止，&lt;strong&gt;已有 12 個州&lt;/strong&gt;（加州、科羅拉多、康乃狄克、蒙大拿、內布拉斯加、新罕布夏、紐澤西、明尼蘇達、馬里蘭、德拉瓦、奧勒岡、德州）以法律要求企業必須認 GPC 訊號。不是「建議」，是「必須」。&lt;/p&gt;
&lt;p&gt;同一件事在歐盟的版本，叫做 Digital Omnibus 裡的 &lt;strong&gt;Article 88b&lt;/strong&gt;：使用者可以在瀏覽器或類似工具裡設定一次隱私偏好，網站有義務讀取並遵守，不能再一直跳視窗問。&lt;/p&gt;
&lt;p&gt;歐盟執委會 2025 年秋天提出了這個條文。然後：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2026 年 6 月 18 日，歐盟理事會的立場文件把 Article 88b 整條刪掉了。&lt;/strong&gt; 支持刪除的成員國包括德國、法國和波蘭，背後是廣告產業長期的遊說。&lt;/p&gt;
&lt;p&gt;現在球在歐洲議會手上，還沒表決。這就是為什麼那七個隱私組織現在跳出來，要大家寫信給自己的歐洲議會議員。&lt;/p&gt;
&lt;h3 id=&quot;一個必須誠實補上的但書&quot;&gt;一個必須誠實補上的但書&lt;/h3&gt;
&lt;p&gt;我不想把這件事講成「歐盟終於要幫大家收拾爛攤子」的爽文，因為它沒那麼單純。&lt;/p&gt;
&lt;p&gt;Digital Omnibus 是一包很大的法案，發起這次行動的那些隱私組織自己在網站上明講：&lt;strong&gt;這次改革的其他大部分內容都有問題，會削弱人們的權利&lt;/strong&gt;。他們支持的只有「把同意搬回瀏覽器」這一小塊。&lt;/p&gt;
&lt;p&gt;同一份法案裡，另一個條文 Article 88a 想把幾類情況明文列為免同意，其中包括「網站營運者&lt;strong&gt;僅供自己使用&lt;/strong&gt;的匯總統計」。注意那四個字——「僅供自己使用」。這代表 Google Analytics 這種把資料送到第三方的分析工具，大概還是不在免死名單裡。&lt;/p&gt;
&lt;p&gt;而且就算 88b 過了，法律圈普遍不看好視窗會消失。理由很現實：告知義務還在（所以還是會有一條通知）、收回同意必須跟給予同意一樣容易（所以還是會有介面）、各家瀏覽器的實作又不見得一致（所以網站會做「有訊號就聽、沒訊號就跳視窗」的混合版）。&lt;/p&gt;
&lt;p&gt;Hacker News 上有人點開歐盟自己的官網 european-union.europa.eu，然後留下一句：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;喔……上面有 cookie 同意視窗。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;真的是有諷刺，笑死我了 XD&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;這個評論拿到很多贊同，我覺得它很好地說明了這件事的難度。&lt;/p&gt;
&lt;p&gt;不過平心而論，歐盟這個算是好學生：底部一條窄橫幅、不擋內容，兩顆按鈕一樣大、一樣顏色，沒有把拒絕藏起來。跟前面 heise 和倫敦交通局那兩個比，它已經是模範。差別在哪？在於這個網站本來就沒打算靠追蹤你去賣廣告。&lt;/p&gt;
&lt;h2 id=&quot;台灣讀者最該看的一段你的網站可能根本不需要它&quot;&gt;台灣讀者最該看的一段：你的網站，可能根本不需要它&lt;/h2&gt;
&lt;p&gt;前面講的是歐洲。現在講你。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;台灣的個資法，沒有 cookie 專章，也沒有任何條文規定網站必須放同意視窗。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果你的訪客都在台灣，法律上不強制。就這麼簡單。&lt;/p&gt;
&lt;p&gt;那為什麼一堆台灣網站都有？我看過的原因大概三種：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;佈景主題或建站平台附送的&lt;/strong&gt;，裝好就在那，沒人去關。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外包廠商順手加的&lt;/strong&gt;，「這是標配啦，加了比較保險」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;看別人有就跟著裝&lt;/strong&gt;，總覺得裝了比較安心、比較專業。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第三種最常見，也最沒必要。你為了「看起來合規」，主動幫自己網站加了一塊擋板、拖慢了載入速度、趕跑了一部分訪客——而法律根本沒要求你這麼做。&lt;/p&gt;
&lt;p&gt;那什麼時候是真的需要？用這張圖判斷：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/hackernews-kill-the-cookie-banner-2026-07-27/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;真的需要的情況&lt;/strong&gt;：你有歐盟訪客，而且你的網站上掛了 GA4、Meta Pixel、廣告 SDK、第三方 A/B 測試工具，或是用標準網址嵌入的 YouTube 影片。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不需要的情況&lt;/strong&gt;：你只有登入 session 和購物車這類功能性 cookie；或者你的分析只看伺服器 log；或者你用的是不寫 cookie 的分析工具。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但我必須誠實說一件事&lt;/strong&gt;：最後那一項——「無 cookie 分析就免同意」——並不是鐵板一塊。&lt;/p&gt;
&lt;p&gt;Plausible、Fathom、Umami、Cloudflare Web Analytics 這些工具都宣稱「不需要 cookie 同意視窗」，而在實務上，絕大多數情況確實沒問題。但反對的一派會說：ePrivacy 管的是「存取裝置上的資訊」，不是「有沒有寫 cookie」，法條裡也沒有一條寫著「匿名化就免同意」。這個爭論到今天還沒有定論，前面提到的 Article 88a 就是想把它寫死——但它現在還只是提案。&lt;/p&gt;
&lt;p&gt;所以誠實的說法是：&lt;strong&gt;換成無 cookie 分析，不等於百分之百免疫，但它把風險從「幾乎確定要同意」降到「絕大多數情況不用」&lt;/strong&gt;。對絕大多數個人網站和中小企業來說，這個交換非常划算。&lt;/p&gt;
&lt;p&gt;順帶一提，台灣 2025 年 10 月通過的個資法修正案要設立獨立的「個人資料保護委員會」，施行日期還沒公布。方向很明確，是往國際標準靠。今天不強制，不代表三年後不強制。&lt;/p&gt;
&lt;h2 id=&quot;三十分鐘實際盤點你自己的網站&quot;&gt;三十分鐘，實際盤點你自己的網站&lt;/h2&gt;
&lt;p&gt;講完概念，來點能動手的。這段不用工程背景也做得到。&lt;/p&gt;
&lt;h3 id=&quot;第一步看清楚誰在寫東西&quot;&gt;第一步：看清楚誰在寫東西&lt;/h3&gt;
&lt;p&gt;開一個無痕視窗，打開你的網站，按 F12 開開發者工具，切到 &lt;strong&gt;Application&lt;/strong&gt; 分頁，左邊點 &lt;strong&gt;Cookies&lt;/strong&gt; 和 &lt;strong&gt;Local Storage&lt;/strong&gt;，重新整理頁面。&lt;/p&gt;
&lt;p&gt;會跑出一份清單。裡面每一項你都要能回答一個問題：&lt;strong&gt;這是誰寫的、為了什麼&lt;/strong&gt;？&lt;/p&gt;
&lt;p&gt;認得出來的（登入、購物車、語言）留著。認不出來的，就是你要處理的對象。&lt;/p&gt;
&lt;h3 id=&quot;第二步看清楚你連去了哪裡&quot;&gt;第二步：看清楚你連去了哪裡&lt;/h3&gt;
&lt;p&gt;切到 &lt;strong&gt;Network&lt;/strong&gt; 分頁，重新整理，看看你的網站到底跟哪些外部網域講了話。&lt;/p&gt;
&lt;p&gt;這一步常常會嚇到人。很多網站的經營者根本不知道自己的頁面連去了七八個第三方——字型、地圖、留言系統、客服 widget、社群分享按鈕、追蹤像素，每一個都在把訪客的 IP 送到別人的伺服器。&lt;/p&gt;
&lt;p&gt;如果懶得自己看，也有現成的掃描工具（webbkoll、Blacklight 之類）幫你列清單。&lt;/p&gt;
&lt;h3 id=&quot;第三步一項一項換掉&quot;&gt;第三步：一項一項換掉&lt;/h3&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;你現在用的&lt;/th&gt;&lt;th&gt;換成&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Google Analytics 4&lt;/td&gt;&lt;td&gt;Cloudflare Web Analytics（免費）、Plausible、Umami（可自架）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Google Fonts 直連 CDN&lt;/td&gt;&lt;td&gt;把字型檔下載下來自己放&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;標準 YouTube 嵌入&lt;/td&gt;&lt;td&gt;&lt;code&gt;youtube-nocookie.com&lt;/code&gt;，或做成「點了才載入」的縮圖&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;留言、地圖、客服 widget&lt;/td&gt;&lt;td&gt;同樣改成點擊才載入&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;字型那條特別值得講一下。2022 年德國慕尼黑地方法院判過一個案子：網站直接連 Google 的 CDN 載入字型，等於把訪客的 IP 傳到美國，法院判賠了 100 歐元。金額不大，但它建立了一個很清楚的原則——&lt;strong&gt;你以為只是載入一個字型檔，法律看到的是你把訪客的 IP 送給了第三方&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;自己放字型不但沒有這個問題，通常還比較快。&lt;/p&gt;
&lt;h3 id=&quot;第四步全部換完之後把視窗整個拿掉&quot;&gt;第四步：全部換完之後，把視窗整個拿掉&lt;/h3&gt;
&lt;p&gt;這才是終點。不是「做一個更好的同意視窗」，是&lt;strong&gt;不再需要同意視窗&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;如果你真的非掛不可至少把它做得像個人&quot;&gt;如果你真的非掛不可，至少把它做得像個人&lt;/h2&gt;
&lt;p&gt;有些人是真的躲不掉——廣告是主要收入、或者集團有統一規範。那退一步，至少做到這幾件事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;拒絕要跟接受一樣簡單。&lt;/strong&gt; 同一層、同樣大小、同樣顯眼。這不是我的個人偏好，法律本來就這樣要求（收回同意必須跟給予同意一樣容易）。你把拒絕藏起來，那個視窗本身就違法了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不要預設打勾。&lt;/strong&gt; 前面說過，45% 的網站在這一條上直接違規。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不要用「正當利益」偷渡。&lt;/strong&gt; 使用者按了拒絕就是拒絕，不要在第二個分頁藏 137 家合作夥伴等他一個一個關。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;拒絕之後，別再一直問。&lt;/strong&gt; Digital Omnibus 的提案版本寫了具體數字：同一個目的被拒絕後，六個月內不得再問。就算法還沒過，這個標準也值得先做。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;還有一件很多人沒算過的帳：效能。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;現成的同意管理平台（CMP）不是免費午餐，實測數字滿驚人的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OneTrust 會塞進 &lt;strong&gt;200KB 以上的 JavaScript&lt;/strong&gt;，而且某些設定下是同步載入的——意思是網站會停在那裡等它跑完。&lt;/li&gt;
&lt;li&gt;CookieYes 曾被測出注入 &lt;strong&gt;48,000 個 DOM 節點&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;Osano 的點擊回應時間達到 &lt;strong&gt;275 毫秒&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;這些數字會直接反映在 Core Web Vitals 上：LCP 變慢（那塊擋板常常變成頁面上最大的元素）、INP 變差（大量的 DOM 操作和事件監聽器）、版面在載入時跳來跳去。&lt;/p&gt;
&lt;p&gt;把這些串起來看，很多網站的處境其實有點荒謬：&lt;strong&gt;為了一個法律沒要求的東西，付出了實實在在的效能代價，而那個東西本身可能還是違法的&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;最後最好的同意視窗是不需要同意視窗&quot;&gt;最後：最好的同意視窗，是不需要同意視窗&lt;/h2&gt;
&lt;p&gt;我知道這篇講了很多法條編號和年份，但整件事其實可以縮成三句話：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;法律說的是「未經同意不准伸手進別人的裝置」，從來沒說「你要放一個視窗」。&lt;/li&gt;
&lt;li&gt;那個視窗是業者為了繼續伸手而發明出來的通行證。&lt;/li&gt;
&lt;li&gt;所以如果你的手本來就沒伸出去，你根本不需要那張通行證。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;回到門口那張桌子的比喻。這十年來，整個產業都在爭論「同意書要怎麼寫才合法」、「桌子要擺多大」、「客人簽名要簽幾次」。&lt;/p&gt;
&lt;p&gt;但一直有一個更簡單的選項擺在那裡，只是很少人選：&lt;strong&gt;把桌子收掉，不要翻客人的包包&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;對絕大多數的個人網站、部落格、公司官網來說，這個選項不但可行，而且更快、更乾淨、更省事。你不需要 CMP，不需要每年續約，不需要擔心它拖慢你的 LCP，也不需要擔心哪天被檢舉說你的拒絕鍵藏太深。&lt;/p&gt;
&lt;p&gt;你只需要少收一點你本來就用不到的資料。&lt;/p&gt;
&lt;p&gt;至於歐洲那邊的仗——現在球在歐洲議會手上，六個月後我們大概就會知道，那個「設定一次、全網通用」的開關到底會不會存在。老實說我不太樂觀，因為 DNT 已經死過一次了。&lt;/p&gt;
&lt;p&gt;但這次至少有一個明顯的不同：&lt;strong&gt;GPC 背後站著法律&lt;/strong&gt;，而 DNT 當年背後什麼都沒有。這個差別可能決定一切。&lt;/p&gt;
&lt;p&gt;而在那之前，你自己的網站要不要放那塊擋板，主導權從頭到尾都在你手上。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;延伸閱讀：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;行動本身：&lt;a href=&quot;https://killthecookiebanner.eu/&quot;&gt;Kill The Cookie Banner&lt;/a&gt;（含寫信給歐洲議會議員的行動頁）&lt;/li&gt;
&lt;li&gt;Hacker News 討論串：&lt;a href=&quot;https://news.ycombinator.com/item?id=49057175&quot;&gt;Kill The Cookie Banner&lt;/a&gt;（1,148 分、591 則留言）&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bitecode.dev/p/there-is-no-eu-cookie-banner-law&quot;&gt;There is no EU cookie banner law&lt;/a&gt;——把「法律沒叫你放視窗」這件事講得最清楚的一篇&lt;/li&gt;
&lt;li&gt;noyb 的暗黑模式報告：&lt;a href=&quot;https://noyb.eu/sites/default/files/2024-07/noyb_Cookie_Report_2024.pdf&quot;&gt;Cookie Banner Report 2024&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;律所視角的 Digital Omnibus 分析：&lt;a href=&quot;https://www.osborneclarke.com/insights/digital-omnibus-reshapes-eu-cookie-rules-leaves-banner-fatigue-largely-intact&quot;&gt;Osborne Clarke&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CMP 效能實測：&lt;a href=&quot;https://www.debugbear.com/blog/consent-management-platforms-performance-comparison&quot;&gt;DebugBear&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.Z6lmsUE6.webp" medium="image"/><category>隱私</category><category>GDPR</category><category>Cookie</category><category>網站經營</category><category>daily-trend</category><category>hackernews</category><enclosure url="https://bobochen.dev/_astro/cover.Z6lmsUE6.webp" length="0" type="image/png"/></item><item><title>Agent 記性很差？ 如何讓不同電腦上的 Agent 進行 context 同步</title><link>https://bobochen.dev/blog/cross-machine-agent-context-sync/</link><guid isPermaLink="true">https://bobochen.dev/blog/cross-machine-agent-context-sync/</guid><description>換一台電腦、開一個新對話，Agent 還是能在幾秒內接上進度。關鍵是把「檔案同步」和「上下文同步」拆成兩件事，再把 source of truth 設計成跨機器、跨對話、跨 agent 都能 reload。</description><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;這題我斷斷續續搞了一個月，踩掉不少坑，最後能濃縮成一句話：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把「檔案同步」和「上下文同步」分開，它們是兩件事。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;會卡住，通常是因為把這兩件事混在一起想。拆開之後，一半的問題會自己消失。&lt;/p&gt;
&lt;h2 id=&quot;檔案同步最簡單的那一半&quot;&gt;檔案同步：最簡單的那一半&lt;/h2&gt;
&lt;p&gt;程式碼、文件這種「檔案」的同步其實沒什麼好講：Google Drive、Dropbox、一個 GitHub repo，挑你順手的就好。這部分是 solved problem，不是你真正的痛點。&lt;/p&gt;
&lt;p&gt;真正的痛點是下一段。&lt;/p&gt;
&lt;h2 id=&quot;上下文同步你以為-agent-很笨其實是它每次重開機&quot;&gt;上下文同步：你以為 Agent 很笨，其實是它每次重開機&lt;/h2&gt;
&lt;p&gt;Agent 本質上是 stateless 的。每開一個新對話，它都從零開始——這不是 bug，是設計。&lt;/p&gt;
&lt;p&gt;你覺得它「笨」「記性差」，多半是因為每次都要把「上次做到哪、為什麼這樣決定、下一步是什麼」重新貼一遍。換一台電腦更慘，連要貼的素材都在另一台上。&lt;/p&gt;
&lt;p&gt;解法不是去期待 agent「記得」，而是&lt;strong&gt;讓結構化的 markdown 幫它 reload&lt;/strong&gt;。我現在用三層：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;CURRENT_WORK.md&lt;/code&gt;（主入口）&lt;/strong&gt;：只放 3–5 個 active 專案，每個 5–6 行，寫「停在哪 + 下一步」。刻意保持短，因為它每天都要讀。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;project_status_&amp;#x3C;專案&gt;.md&lt;/code&gt;&lt;/strong&gt;：單一專案的最新狀態，細節放這。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;handoff.md&lt;/code&gt;&lt;/strong&gt;：只有大型專案才需要，記決策、風險、驗證細節這種「為什麼」的東西。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;開新對話時我只要打一句「開工」，agent 自動把這三層讀進來，幾秒就接上狀態，不用再貼歷史。&lt;/p&gt;
&lt;p&gt;整套設計不是把舊對話搬來搬去，而是讓每個新 session 都從同一份外部狀態重新載入：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/cross-machine-agent-context-sync/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;圖上的閉環才是關鍵：同步 repo 只解決「檔案到了」，原生載入機制解決「agent 看到了」，收工前回寫則避免下一個 session 讀到過期停點。&lt;/p&gt;
&lt;h3 id=&quot;跨機器用一個-private-repo-當單一真相&quot;&gt;跨機器：用一個 private repo 當單一真相&lt;/h3&gt;
&lt;p&gt;三層檔案我放在一個 GitHub repo（我叫它 &lt;code&gt;claude-memory&lt;/code&gt;），兩台電腦都 clone 它。Google Drive 也能做到同步，但 GitHub 多了 diff history，改了什麼一目瞭然，對「決策記錄」這種東西特別有用。&lt;/p&gt;
&lt;p&gt;一個容易漏掉的點：&lt;strong&gt;這個 repo 一定要 private。&lt;/strong&gt; 裡面裝的是你專案的內部決策、風險、還沒公開的進度，不是能隨便攤在外面的東西。&lt;/p&gt;
&lt;p&gt;至於「怎麼讓 agent 讀到這些檔案」，這裡我要修正一個自己一開始的做法——&lt;/p&gt;
&lt;p&gt;我原本寫了一支 sync 腳本，把檔案「灌」進 agent 的 local memory 目錄。後來發現大多數情況不必這麼麻煩，因為工具本身就有原生機制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; 的 &lt;code&gt;@&lt;/code&gt; import 支援絕對路徑。你可以在 &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; 或專案的 &lt;code&gt;CLAUDE.md&lt;/code&gt; 裡直接寫 &lt;code&gt;@~/repos/claude-memory/CURRENT_WORK.md&lt;/code&gt;，clone 一次之後就自動載入，不用每次手動同步。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codex&lt;/strong&gt; 現在有原生 memory：session 開始會整份讀 &lt;code&gt;memory_summary.md&lt;/code&gt;，需要細節時再 grep &lt;code&gt;MEMORY.md&lt;/code&gt;。結構跟我手刻的三層幾乎一樣。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;換句話說，你手刻的三層 markdown 是很好的&lt;strong&gt;內容結構&lt;/strong&gt;，值得留著；但「載入」這一步可以交給工具原生的 import / memory，少維護一支腳本。而且兩家工具都在往「原生 memory」補齊——這反而印證了 stateless 的方向沒錯：與其讓模型記得，不如讓外部檔案記得。&lt;/p&gt;
&lt;h3 id=&quot;兩個沒人會先告訴你的坑&quot;&gt;兩個沒人會先告訴你的坑&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;坑一：merge conflict 是常態。&lt;/strong&gt; 兩台電腦都在改同一份 &lt;code&gt;CURRENT_WORK.md&lt;/code&gt;，又靠 git 同步，衝突遲早會來——尤其這份檔案每個 session 都在變。我的處理方式是：&lt;code&gt;CURRENT_WORK.md&lt;/code&gt; 用「per-machine 區塊」分開寫，或乾脆接受偶爾手動 resolve；&lt;code&gt;handoff.md&lt;/code&gt; 盡量 append-only（往下加、不改舊的），衝突機率就低很多。這是這套系統真實存在的 friction，別假裝沒有。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坑二：停點過期，比沒有記憶更糟。&lt;/strong&gt; 如果你忘了更新停點，agent 會「很有信心地」進入一個&lt;strong&gt;錯誤&lt;/strong&gt;的狀態——confidently wrong，比它老實說「我不知道」還危險。所以檔案的鮮度其實是這套系統的單點失敗。下面 Q2 的 baton 規則就是在擋這件事。&lt;/p&gt;
&lt;h2 id=&quot;codex-和-claude-code-怎麼協作&quot;&gt;Codex 和 Claude Code 怎麼協作&lt;/h2&gt;
&lt;p&gt;我在公司環境同時跑 Codex 和 Claude Code。協作的第一原則跟上面一樣：&lt;strong&gt;共享 source of truth，不要分資料匣。&lt;/strong&gt; 分了，你就要兩邊維護重複資訊，只會更累。&lt;/p&gt;
&lt;h3 id=&quot;修正frontmatter-tag-是約定不是機制&quot;&gt;修正：frontmatter tag 是「約定」，不是「機制」&lt;/h3&gt;
&lt;p&gt;我一開始想用 markdown frontmatter tag 來區分「誰看哪份檔」，像這樣：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;---
agents: [claude, codex]
codex_tier: summary
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;這裡要老實講一個我踩過的誤解：&lt;strong&gt;這兩家工具都不會自動解析這種自訂 frontmatter。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Codex 是靠走訪 &lt;code&gt;AGENTS.md&lt;/code&gt;（從 git root 往下逐層串接）決定讀什麼，不看 &lt;code&gt;agents:&lt;/code&gt; 或 &lt;code&gt;codex_tier:&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Claude Code 是靠 &lt;code&gt;CLAUDE.md&lt;/code&gt; 階層加上 &lt;code&gt;@path&lt;/code&gt; import 決定讀什麼，同樣不看這些 tag。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是說，照上面那樣寫，&lt;strong&gt;兩家其實都會把整份檔讀進去，tag 完全沒作用&lt;/strong&gt;，除非你另外寫一支腳本去解析它、或在 prompt 裡明講「請遵守 frontmatter」。如果你只是把它當成「給人看的註記」那沒問題；但別誤以為它會自動分流。&lt;/p&gt;
&lt;p&gt;真正能「一份真相、兩家都吃」的做法，是用工具原生的慣例：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;寫一份 &lt;strong&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/strong&gt; 放兩邊共用的 repo 規則（Codex 原生讀，Cursor、Aider 等工具也吃這套）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; 只寫 &lt;code&gt;@AGENTS.md&lt;/code&gt; 加上 Claude 專屬的補充&lt;/strong&gt;——Claude Code 不直接讀 AGENTS.md，但可以 &lt;code&gt;@import&lt;/code&gt; 進來。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這樣是工具自動載入、真正單一真相，不靠任何 tag 約定。這一段我在 &lt;a href=&quot;https://bobochen.dev/blog/share-config-memory-claude-md-agents-md/&quot;&gt;Claude Code 與 Codex 共用專案規則&lt;/a&gt; 有更完整的拆解。&lt;/p&gt;
&lt;h3 id=&quot;分工不是誰比較強是誰適合這一步&quot;&gt;分工：不是誰比較強，是誰適合這一步&lt;/h3&gt;
&lt;p&gt;一個月觀察下來，我的實際分工是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;——規劃、多步推理、cross-cutting 的 design review、UI，偏「想清楚再動手」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codex&lt;/strong&gt;——執行力強、code edit 快、腳本批改、Windows 工作流（有些需求還是得在 Windows 上操作）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但「誰上場」與其看誰強，不如看任務的權限、風險和工作介面來決定，這部分我另外寫過一篇 &lt;a href=&quot;https://bobochen.dev/blog/why-dual-master-claude-code-codex-mental-models/&quot;&gt;Claude Code 和 Codex 怎麼分工&lt;/a&gt;。&lt;/p&gt;
&lt;h3 id=&quot;baton-規則交接前先寫進檔案&quot;&gt;baton 規則：交接前先寫進檔案&lt;/h3&gt;
&lt;p&gt;跨 agent 接手前，先把「做到哪、下一步」寫進 markdown 留存，再對人類口頭補一句話交接。這條規則同時擋掉上面的「坑二」：因為每次交接都逼你更新停點，跨日、跨機器就不會 drift。&lt;/p&gt;
&lt;p&gt;這裡也順手修掉一個我原本的自我矛盾：我除了 &lt;code&gt;CURRENT_WORK.md&lt;/code&gt;，還手動維護一份超精簡的 &lt;code&gt;CURRENT_WORK.codex.md&lt;/code&gt;——但這其實違反了「不要維護兩份重複資訊」的原則。後來我改成讓精簡版從主檔&lt;strong&gt;衍生&lt;/strong&gt;（用腳本產，或用上面 &lt;code&gt;AGENTS.md&lt;/code&gt; 的分層讓它自動精簡），而不是手動同步兩份。如果你也想給 Codex 一份更短的版本，記得讓它是「產物」不是「複本」。&lt;/p&gt;
&lt;h3 id=&quot;三角-review拉一個-fresh-eye-進來&quot;&gt;三角 review：拉一個 fresh eye 進來&lt;/h3&gt;
&lt;p&gt;想比較同一個 task 兩家的差異，最簡單就是同題目同時丟、對比結果。&lt;/p&gt;
&lt;p&gt;我做過更進一步的：在 design review 階段，刻意拉 &lt;strong&gt;Claude 網頁版&lt;/strong&gt;當第三隻眼，去看 Claude Code + Codex 產出的架構。那次 Claude 網頁版抓出 8 條 cross-cutting 的結構性風險，是另外兩邊都沒看到的。原因不是它比較聰明，而是它的上下文是乾淨的、被迫重新建立假設——這跟交叉 review 有效的理由是同一個。關於「兩個 agent 互相 review 為什麼有效、又為什麼不是雙重保證」，我在 &lt;a href=&quot;https://bobochen.dev/blog/claude-code-codex-cross-review-collaboration-workflow/&quot;&gt;讓 Claude Code 和 Codex 互相 Review&lt;/a&gt; 講得更細。&lt;/p&gt;
&lt;h2 id=&quot;一句話總結&quot;&gt;一句話總結&lt;/h2&gt;
&lt;p&gt;把 source of truth 設計成&lt;strong&gt;跨對話、跨機器、跨 agent 都能 reload&lt;/strong&gt;，上面那些 friction 就會自動少一大半。剩下的（merge conflict、停點鮮度）不會消失，但都有明確的處理方式。&lt;/p&gt;
&lt;p&gt;Agent 不需要記得，讓檔案幫助它記得。&lt;/p&gt;
&lt;p&gt;就跟我們人類一樣，大腦不是用來記得任何事，所以我們平常會寫筆記、螢幕旁也佈滿便條貼不是嗎? (笑)&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.CLr-HS4q.webp" medium="image"/><category>Claude Code</category><category>Codex</category><category>AI Coding</category><category>CLI</category><category>工作流</category><enclosure url="https://bobochen.dev/_astro/cover.CLr-HS4q.webp" length="0" type="image/png"/></item><item><title>把工作交給 AI 的四個段位：5 分鐘學會 Agent Loop</title><link>https://bobochen.dev/blog/four-agent-loops-in-5-minutes/</link><guid isPermaLink="true">https://bobochen.dev/blog/four-agent-loops-in-5-minutes/</guid><description>一句話講清楚 Loop 是什麼，四種 Loop（Turn-based、Goal-based、Time-based、Proactive）各自的適合場景、最佳實踐、上手案例、常見錯誤，最後附一個可以直接抄的設計模板。從 Claude Code 團隊的 loops 文章消化而來，不寫程式也用得上。</description><pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;這是「Agentic Engineering 實戰手冊」系列第十六篇。上一篇：&lt;a href=&quot;https://bobochen.dev/blog/subagent-staged-build-taiwansalary&quot;&gt;一個 Session、7 個 Subagent：TaiwanSalary 分階段實作紀錄&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;2026 年 6 月 30 日，Claude Code 團隊發表了 &lt;a href=&quot;https://claude.com/blog/getting-started-with-loops&quot;&gt;Getting started with loops&lt;/a&gt;。文章先處理一個常見困惑：大家都在說「不要只下 prompt，要設計 loop」，可是不同人講的 loop 根本不是同一件事。&lt;/p&gt;
&lt;p&gt;我讀完的感覺是：終於有人把我這半年土法煉鋼摸出來的東西，用四個格子裝好了。我自己天天在用這些 loop——讓 AI 每五分鐘盯我的 PR、派一排 subagent 蓋網站、排程跑每天的例行檢查——但要跟別人解釋的時候，一直講不出一個乾淨的分類。&lt;/p&gt;
&lt;p&gt;所以這篇不是翻譯，是消化。就算你不寫程式，只要有在用 AI 做事，看完就能直接拿去用。&lt;/p&gt;
&lt;h2 id=&quot;一句話loop-是什麼&quot;&gt;一句話：Loop 是什麼&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Loop 就是：Agent 重複執行「觀察、行動、檢查」的循環，直到停止條件成立。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一般對話常是「你說一句、它做一輪、停下來等你」。設計 loop 時，除了做什麼，還要講清楚由什麼觸發下一輪、怎麼驗收、什麼時候停，以及何時升級給人。&lt;/p&gt;
&lt;p&gt;我覺得最貼切的比喻是帶助理。四種 loop，其實就是你把手上的四樣東西，一件一件交出去：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先交出&lt;strong&gt;檢查&lt;/strong&gt;——教它怎麼驗收自己的工作&lt;/li&gt;
&lt;li&gt;再交出&lt;strong&gt;停止條件&lt;/strong&gt;——講好做到什麼程度才算完&lt;/li&gt;
&lt;li&gt;再交出&lt;strong&gt;觸發時機&lt;/strong&gt;——講好什麼時候該開工&lt;/li&gt;
&lt;li&gt;最後連&lt;strong&gt;任務本身&lt;/strong&gt;都交出去——事情來了它自己接&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;交得越多，不代表一定越輕鬆；你也接手了更多監控與風險管理。每一層先小量跑穩，再往下交。&lt;/p&gt;
&lt;p&gt;如果你手上已經有一件工作，可以先用這棵決策樹定位，不必先背四個名詞：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/four-agent-loops-in-5-minutes/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這不是成熟度排名。它只是在回答兩件事：誰負責啟動下一輪，以及 agent 可以自主跑到哪裡。&lt;/p&gt;
&lt;h2 id=&quot;第一種turn-based你叫一下它動一下&quot;&gt;第一種：Turn-based——你叫一下，它動一下&lt;/h2&gt;
&lt;p&gt;這是最常見的互動方式：你下一個指令，它理解、動手、檢查、回報，然後停下來等你。每一句話都是一圈小 loop，下一輪仍由你觸發。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;適合場景&lt;/strong&gt;：一次性的事、你還在摸索方向的事、做完需要你判斷下一步的事。改一份文件、查一個問題、修一個小東西。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳實踐&lt;/strong&gt;：把「你會怎麼檢查」一起交出去。多數人只交代「做什麼」，不交代「怎樣算做好」，於是驗收全落回自己頭上。檢查方式寫得越具體、越能打勾，它就越能自己抓自己的錯，你來回的次數就越少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;馬上上手&lt;/strong&gt;：不要說「幫我改履歷」。說「幫我改履歷。改完自己檢查三件事：一頁以內、每段經歷都有數字成果、沒有錯字。任何一項沒過就自己再改，全過了才拿給我。」我的工程師版本是把驗收步驟寫成一個清單檔，每次 AI 改完介面就照著跑一遍：打開頁面、實際點那顆按鈕、截圖前後對比、確認沒有噴錯誤——不准只憑「我改好了」三個字交差。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常見錯誤&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;指令模糊，來回十次。來回的每一次都是你的時間。划算的做法是把成本花在第一句話上。&lt;/li&gt;
&lt;li&gt;它說「做完了」你就信了。AI 對「做完」的標準比你寬鬆得多，沒給檢查清單，它交回來的是「它覺得可以」的版本。&lt;/li&gt;
&lt;li&gt;每天重複的事還在用一問一答推。那是下面三種 loop 的工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;第二種goal-based講好終點讓它自己跑到&quot;&gt;第二種：Goal-based——講好終點，讓它自己跑到&lt;/h2&gt;
&lt;p&gt;一次做不到位的事，可以讓它反覆試到達標。這層交出去的是&lt;strong&gt;停止條件&lt;/strong&gt;：達標就停，超過次數／時間或沒有進展也要停。在 Claude Code 裡，&lt;a href=&quot;https://code.claude.com/docs/en/goal&quot;&gt;&lt;code&gt;/goal&lt;/code&gt;&lt;/a&gt;會在每輪結束後交給另一個小模型判定條件是否成立；這個 evaluator 不會自己跑指令，只能根據對話裡出現的證據判斷，所以驗證結果要明確寫進 transcript。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;適合場景&lt;/strong&gt;：你講得出「怎樣算完成」而且能客觀判定的任務。分數達標、測試全過、字數壓進上限、清單清空。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳實踐&lt;/strong&gt;：終點用「可以打勾」的句子寫，而且永遠設停損。「更快」不行，「3 秒內打開」可以；「更專業」不行，「每一段都有數據佐證」可以。然後補一句：「最多試 5 次，試滿還沒過就停下來，告訴我卡在哪。」&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;馬上上手&lt;/strong&gt;：「把這份簡報稿修到唸一遍在 3 分鐘以內、每頁只有一個重點，最多修 5 輪。」我的版本是原文那個例子，也是我真的會下的指令：「把首頁的 Lighthouse 分數弄到 90 以上，最多試 5 次。」&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常見錯誤&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目標是形容詞。「好看」「順一點」沒辦法判定，它只能自己猜一個標準，然後太早收工。&lt;/li&gt;
&lt;li&gt;不設次數上限。沒有停損的 loop 會一直燒，卡住的時候燒得特別兇。&lt;/li&gt;
&lt;li&gt;目標被作弊達成。上有政策，下有對策：你叫它「讓測試全過」，它可能把失敗的測試直接關掉；叫它「摘要壓到 100 字」，它可能把重點全刪光。我的全域規則裡有一條「絕不准跳過失敗的測試」，就是被這種事逼出來的。定目標時多想一步：這個目標有沒有歪路可走？有，就把歪路堵起來寫進規則。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;第三種time-based固定時間自己開工&quot;&gt;第三種：Time-based——固定時間自己開工&lt;/h2&gt;
&lt;p&gt;這層交出去的是&lt;strong&gt;觸發時機&lt;/strong&gt;。每隔一段時間做一次，或每天固定時刻做一次；停的方式是手動取消、工作完成，或碰到預設停損。Claude Code 裡的 &lt;a href=&quot;https://code.claude.com/docs/en/scheduled-tasks&quot;&gt;&lt;code&gt;/loop&lt;/code&gt;&lt;/a&gt;在本機、目前 session 中執行；&lt;code&gt;/schedule&lt;/code&gt;建立獨立的 cloud routine。兩者的執行環境、可存取檔案、權限與最小間隔不同，不能只把它們理解成「本機版／雲端版同一功能」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;適合場景&lt;/strong&gt;：任務不變、只有輸入在變的例行事；或需要盯著外面某個會變的東西、隨時反應的事。每天早上的訊息摘要屬於前者，盯著送出去的提案等回音屬於後者。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳實踐&lt;/strong&gt;：間隔跟著「那個東西多久變一次」走，不是跟著你的焦慮走。另外，設 loop 的當下就先想好它的死法——什麼情況下這個 loop 該停。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;馬上上手&lt;/strong&gt;：「每天早上 8 點，把我訂閱的三個主題的新消息整理成 10 行以內的摘要。」我的版本是 &lt;code&gt;/loop 5m&lt;/code&gt;：每 5 分鐘看一下我的 PR，有 review 意見就處理、CI 掛了就修。盯 PR 這種一小時內會變好幾次的事，5 分鐘合理；同樣的間隔拿去盯一天才更新一次的報表，就純粹是浪費。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常見錯誤&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;間隔比變化快。一天更新一次的東西，不需要每 5 分鐘查。&lt;/li&gt;
&lt;li&gt;忘記關。事情早就結束了，loop 還在那邊空轉。&lt;/li&gt;
&lt;li&gt;開在自己電腦上，卻期待它長期跑。&lt;code&gt;/loop&lt;/code&gt; 需要 Claude Code session 保持運行；長期任務可評估 cloud routine、Desktop scheduled task 或 CI，但要重新設定執行環境與權限。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;第四種proactive事情來了它自己接&quot;&gt;第四種：Proactive——事情來了，它自己接&lt;/h2&gt;
&lt;p&gt;這一層連&lt;strong&gt;任務入口&lt;/strong&gt;也交出去：事件發生或排程到了，workflow 自動接手，再用目標與檢查決定怎麼處理。它不一定比前三種「高級」，只是自主範圍更大、風險也更高。&lt;/p&gt;
&lt;p&gt;它其實不是一個新東西，而是前三層的組合：排程當觸發、目標當驗收、檢查清單當品管，再加上「不用每一步都跟你要許可」的授權。原文的例子是每小時掃一次回報頻道，每一筆問題自動分類、處理、回覆，處理的時候還同時開三個方案互相比、找一個裁判來挑最好的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;適合場景&lt;/strong&gt;：源源不絕、規格明確的重複工作流。客服信件的初步回覆、資料整理歸檔、問題回報的分類分派——那種「靠規則就能做掉八成」的事。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最佳實踐&lt;/strong&gt;：先小量試跑，然後讓便宜的做例行、貴的做判斷。第一天先讓它處理 5 件，你逐件看過、把規則補完，再放大量。例行動作交給快而便宜的模型，需要判斷的關卡才動用最強的——不然帳單會教你做人。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;馬上上手&lt;/strong&gt;：從半自動開始：「收到詢價信時，按這份價目表草擬回覆，放進草稿匣。」你負責查核後送出。是否把「按送出」也自動化，不能只看它跑順幾次；還要看品牌、法務、個資、錯寄與冒犯風險。對外訊息預設保留核准，通常是合理的長期設計。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常見錯誤&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一天就全放手。自動化不會讓錯誤變少，只會讓錯誤變快、變多。&lt;/li&gt;
&lt;li&gt;驗收標準空白。前三層沒站穩就跳到這層，等於開一條量產瑕疵品的產線。&lt;/li&gt;
&lt;li&gt;不留抽查點。再穩的產線也要留人工抽查，尤其是對外的動作——寄信、發文、回客戶，錯一次就是別人看到。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;一張表收束&quot;&gt;一張表收束&lt;/h2&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Loop&lt;/th&gt;&lt;th&gt;你交出去的是&lt;/th&gt;&lt;th&gt;什麼時候用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Turn-based&lt;/td&gt;&lt;td&gt;檢查&lt;/td&gt;&lt;td&gt;一次性的事，你還在摸索&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Goal-based&lt;/td&gt;&lt;td&gt;停止條件&lt;/td&gt;&lt;td&gt;你講得出「怎樣算完成」&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Time-based&lt;/td&gt;&lt;td&gt;觸發時機&lt;/td&gt;&lt;td&gt;事情定期發生，或要盯著外部變化&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Proactive&lt;/td&gt;&lt;td&gt;任務入口&lt;/td&gt;&lt;td&gt;規格明確、持續進件的重複工作流&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;直接抄的-loop-設計模板&quot;&gt;直接抄的 Loop 設計模板&lt;/h2&gt;
&lt;p&gt;挑一件你已經在做、而且覺得煩的事，把下面六格填完。填不出來的那一格，就是你還不能交出去的那一層。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;【任務】一句話說清楚要做什麼：
　______________________________

【完成的樣子】可以打勾的判定條件（禁止形容詞）：
　1. ______________________________
　2. ______________________________

【怎麼檢查】把「你自己會怎麼驗收」寫成步驟：
　1. ______________________________
　2. ______________________________

【什麼時候做】選一個：
　□ 我叫它才做（Turn-based）
　□ 沒達標就繼續試（Goal-based）
　□ 每隔 ____ 做一次（Time-based）
　□ ______ 發生時自動接手（Proactive）

【停損】最多試 ___ 次；超過 ___ 沒進展就停下來問我；
　遇到 ______________（對外、花錢、刪東西）一律先問我。

【試跑】先拿最小的一份工作跑一輪，記下它卡住的地方
　和做過頭的地方，把規則補進上面五格，再放大。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;填的順序就是我建議的交付順序：先寫檢查，再談目標；目標可判定，再加排程；前面幾層有證據後，才擴大自主範圍。這不是資格考，而是降低一次放太多權限後很難除錯的機率。&lt;/p&gt;
&lt;p&gt;Loop 不是設一次就完美的東西。跑一輪、看它在哪裡停太早、在哪裡衝過頭，修一下再跑。&lt;strong&gt;你在迭代工作，也在迭代這個 loop 本身。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;原文：&lt;a href=&quot;https://claude.com/blog/getting-started-with-loops&quot;&gt;Getting started with loops&lt;/a&gt;，Claude Code 團隊，Delba de Oliveira 與 Michael Segner。功能與命令會更新，實際使用前請再查當前官方文件。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;上一篇：&lt;a href=&quot;https://bobochen.dev/blog/subagent-staged-build-taiwansalary&quot;&gt;TaiwanSalary 分階段實作紀錄&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;完整系列：&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-what-is-it&quot;&gt;Agentic Engineering 實戰手冊&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.DT-LCnoC.webp" medium="image"/><category>Claude Code</category><category>AI Agent</category><category>Agentic Engineering</category><category>工作流程</category><enclosure url="https://bobochen.dev/_astro/cover.DT-LCnoC.webp" length="0" type="image/png"/></item><item><title>一個 Session、7 個 Subagent、1,889 頁：TaiwanSalary 分階段實作紀錄</title><link>https://bobochen.dev/blog/subagent-staged-build-taiwansalary/</link><guid isPermaLink="true">https://bobochen.dev/blog/subagent-staged-build-taiwansalary/</guid><description>把公開資訊觀測站的薪資資料整理成 1,889 頁靜態網站。我讓主 session 負責規格與驗收，7 個 fresh subagent 分階段實作，記錄中斷恢復、資料驗證與 review 真正抓到的問題。</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;這是「Agentic Engineering 實戰手冊」系列第十五篇。上一篇：&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-future-and-you&quot;&gt;Agentic Engineering 的下一步&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;上週日下午，我想查「台積電非主管員工的薪資中位數」，結果卡在&lt;a href=&quot;https://mopsov.twse.com.tw/mops/web/t100sb15&quot;&gt;公開資訊觀測站&lt;/a&gt;的查詢頁：先選年度，再選市場與產業，想比較另一家公司就重來一次。&lt;/p&gt;
&lt;p&gt;於是我做了工程師很常做的事：自己蓋一個。&lt;/p&gt;
&lt;p&gt;當天晚上，&lt;a href=&quot;https://github.com/bobo52310/TaiwanSalary&quot;&gt;TaiwanSalary&lt;/a&gt; 已經是一個 1,889 頁的純靜態網站。依當時 committed data 的快照，資料涵蓋民國 108–114 年、1,847 家曾出現在期間資料裡的上市櫃公司；114 年首頁可查 1,826 家。網站能搜尋、排序，也有公司歷年趨勢頁。README 記錄的 production build 約 3 秒、首頁 gzip 後約 100KB；這些是該專案當時的實測，不是 Astro 專案的普遍保證。&lt;/p&gt;
&lt;p&gt;這篇想記的不是功能，而是它怎麼被做出來：主 session 當工頭，負責規格、驗收與整合；每一階段派一個 fresh subagent 實作。七個階段完成後，git history 也留下七個初始 commit。&lt;/p&gt;
&lt;h2 id=&quot;工頭不負責多寫負責看全局&quot;&gt;工頭不負責多寫，負責看全局&lt;/h2&gt;
&lt;p&gt;我把工作切成資料管線、查詢頁、公司與產業頁、排行榜、SEO、文件等可獨立驗收的階段。每個 subagent 只拿到當前任務需要的 context，不需要背著整個專案一路走到底。&lt;/p&gt;
&lt;p&gt;主 session 做四件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;給清楚的交付物、限制與驗收方式。&lt;/li&gt;
&lt;li&gt;確認 agent 還在工作，遇到中斷就恢復或重派。&lt;/li&gt;
&lt;li&gt;讀核心 diff，跑資料與 build 驗證。&lt;/li&gt;
&lt;li&gt;做跨階段決策，通過才 commit。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這個分工的好處不是「fresh agent 一定比較聰明」，而是每一段 context 較乾淨，出錯時也有 checkpoint。代價則是 handoff：前一個 agent 知道的事，不會自動出現在下一個 agent 腦中，工頭必須把關鍵決策寫下來。&lt;/p&gt;
&lt;p&gt;七個 subagent 不是同時往同一個工作區衝，而是沿著同一套驗收閘門接力：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/subagent-staged-build-taiwansalary/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這張圖裡真正不能省的不是 subagent，而是菱形那道驗收閘門。沒有讀 diff、跑檢查與留下 checkpoint，多開幾個 agent 只會更快把不確定性堆進同一個 repo。&lt;/p&gt;
&lt;h2 id=&quot;那天有兩次subagent-突然沒動靜&quot;&gt;那天有兩次，Subagent 突然沒動靜&lt;/h2&gt;
&lt;p&gt;第一次發生在資料管線階段。Agent 寫完 &lt;code&gt;types.ts&lt;/code&gt; 後，我的 Mac 休眠、連線中斷。回來後，主 session 透過檔案 mtime 發現 27 分鐘沒有新輸出，便傳訊息確認狀態。那次 session context 還在，所以 agent 能從中斷處繼續。&lt;/p&gt;
&lt;p&gt;第二次是後面的 API 連線中斷。我加了一個簡單的背景觀察哨，每三分鐘看一次是否有新檔案或新 log，確認恢復後才讓流程往下。&lt;/p&gt;
&lt;p&gt;這裡不能推出「斷線後 context 通常都會保留」。是否能 resume 取決於工具、session 與中斷方式。真正能複製的做法只有兩個：保留 checkpoint，以及對長時間無輸出設 timeout 與人工升級，不要把沉默當成進度。&lt;/p&gt;
&lt;h2 id=&quot;review-抓到的不只是-agent-的錯&quot;&gt;Review 抓到的，不只是 Agent 的錯&lt;/h2&gt;
&lt;p&gt;每個階段回報完成後，我都會讀核心檔案、跑對應檢查，再決定要不要 commit。這一步抓到兩個值得記的問題。&lt;/p&gt;
&lt;h3 id=&quot;1-工作區裡出現-nul-位元組&quot;&gt;1. 工作區裡出現 NUL 位元組&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;build-data.ts&lt;/code&gt; 曾混入一個 &lt;code&gt;\x00&lt;/code&gt;。它藏在字串裡，肉眼很難發現，而且那次 lint 與 build 沒有因此失敗。我另外掃描控制字元，才在 offset 6967 找到。&lt;/p&gt;
&lt;p&gt;這不代表 agent 永遠看不到 NUL，也無法只憑結果斷定是哪一層寫入機制造成；能確定的是，那次執行沒有主動檢查控制字元。後來我把這類檢查放進資料管線驗證，而不是期待 reviewer 每次「順手想到」。&lt;/p&gt;
&lt;h3 id=&quot;2-重複公司代號會被靜默覆蓋&quot;&gt;2. 重複公司代號會被靜默覆蓋&lt;/h3&gt;
&lt;p&gt;合併同年度、同股票代號資料時，程式原本會只留一筆。現有資料沒有同一家公司同時出現在上市與上櫃的情況，但如果未來來源資料異常，靜默覆蓋會讓錯誤很難追。&lt;/p&gt;
&lt;p&gt;最後補的不是一句提醒，而是一條驗證：遇到重複 key 就讓 build 失敗。Review 最有價值的時刻，常常不是找到語法錯，而是把「目前剛好沒發生」改成可執行的不變量。&lt;/p&gt;
&lt;h2 id=&quot;agent-反過來證明我的規格錯了&quot;&gt;Agent 反過來證明我的規格錯了&lt;/h2&gt;
&lt;p&gt;MOPS 的薪資表版面會隨年度變動。我一開始很有把握地說：「108–112 年的三個旗標欄位，都用 &lt;code&gt;V&lt;/code&gt; 表示 true、空白表示 false。」&lt;/p&gt;
&lt;p&gt;資料 agent 沒有照抄。它掃過 14 份原始資料後回報：108、109 年是 &lt;code&gt;V&lt;/code&gt;／空白；110 年起已經改為 &lt;code&gt;Y&lt;/code&gt;／&lt;code&gt;N&lt;/code&gt;。現在 repo 裡的 &lt;code&gt;parseFlag&lt;/code&gt; 同時接受兩種格式，而 &lt;code&gt;IMPLEMENTATION_PLAN.md&lt;/code&gt; 也留下這個決策。&lt;/p&gt;
&lt;p&gt;如果照我的記憶做，110–112 年的旗標就會解析錯，而且不一定報錯。這次最重要的不是「agent 敢頂嘴」，而是它拿原始資料當證據。規格、人的記憶與模型的回答都只是待驗證假設，來源資料才是這個 parser 的權威。&lt;/p&gt;
&lt;p&gt;資料管線也把不同年度的欄位數明確寫成 layout：108–112 年 16 欄、113 年 19 欄、114 年 31 欄。遇到未知年度或欄位數不符就 loud-fail，不猜欄位。README 提到 115 年預告會新增揭露欄位，因此下一次更新本來就預期要先人工檢查新 layout。&lt;/p&gt;
&lt;h2 id=&quot;專案快照&quot;&gt;專案快照&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1 個主 session、7 個階段、7 個 fresh subagent、7 個初始 commit。&lt;/li&gt;
&lt;li&gt;1,889 個靜態頁面；當時 build 約 3 秒、首頁 gzip 後約 100KB。&lt;/li&gt;
&lt;li&gt;7 個年度 × 上市／上櫃，共 14 份年度資料；期間合併後 1,847 家公司。&lt;/li&gt;
&lt;li&gt;本機 Lighthouse 測試落在 96–100／100／100／100；分數會受頁面、Lighthouse 版本與測試環境影響，所以只當該次快照。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;這套模式為什麼對這個專案有效&quot;&gt;這套模式為什麼對這個專案有效&lt;/h2&gt;
&lt;p&gt;第一，階段之間有清楚的資料依賴，也能獨立驗收。第二，Astro 靜態輸出讓每一步都能用 build 檢查。第三，資料快照 commit 進 repo，上游 MOPS 暫時不可用時，網站仍能建置。&lt;/p&gt;
&lt;p&gt;這不代表「fresh subagent 永遠勝過單一 agent」。如果任務需要大量隱性脈絡、切割後反而一直重讀同一批檔案，subagent 可能增加總 token 與 handoff 錯誤。這次有效，是因為階段邊界清楚，而且主 session 願意做整合工作。&lt;/p&gt;
&lt;p&gt;另外，放手不等於甩手。我會看 timeout、讀 diff、跑 build，也會在規格被資料推翻時重新決策。Agent 做了大部分實作，但最終 merge 的責任沒有移出去。&lt;/p&gt;
&lt;h2 id=&quot;想試-subagent-driven可以先做這四步&quot;&gt;想試 Subagent-Driven，可以先做這四步&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;挑能獨立驗收的階段。&lt;/strong&gt; 不要只按檔案數硬切；每一段都要有交付物、成功條件與驗證命令。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把關鍵決策寫進 repo。&lt;/strong&gt; Plan、schema、ADR 或 README 都可以，別讓下一個 agent 只能靠主 session 口述。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;每段完成就 review 與 checkpoint。&lt;/strong&gt; 讀高風險 diff，執行對應測試，再 commit；不要因 agent 說「全綠」就略過。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;設 timeout 與恢復路徑。&lt;/strong&gt; 長時間無輸出就確認狀態；無法 resume 時，從上一個乾淨 checkpoint 重派。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這個專案還有兩條線值得另外寫：政府開放資料的版面與限流，以及為什麼我選純靜態 + committed JSON snapshot。它們更像資料產品題目，之後會放到台灣開放資料系列。&lt;/p&gt;
&lt;p&gt;如果只留一句話，我會留這句：&lt;strong&gt;subagent 不是多開幾個聊天視窗就會自動變團隊；有人負責切工作、留證據、做驗收，它們才真的能接力。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;上一篇：&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-future-and-you&quot;&gt;Agentic Engineering 的下一步&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;下一篇：&lt;a href=&quot;https://bobochen.dev/blog/four-agent-loops-in-5-minutes&quot;&gt;把工作交給 AI 的四個段位：Agent Loop&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.AywaaRJC.webp" medium="image"/><category>Claude Code</category><category>Agentic Engineering</category><category>Astro</category><category>Side Project</category><category>專案總結</category><enclosure url="https://bobochen.dev/_astro/cover.AywaaRJC.webp" length="0" type="image/png"/></item><item><title>挑機構、問價格：養護中心還是護理之家</title><link>https://bobochen.dev/blog/longterm-care-notes-05-choosing-a-facility/</link><guid isPermaLink="true">https://bobochen.dev/blog/longterm-care-notes-05-choosing-a-facility/</guid><description>出院後要把爸送去哪？安養、養護中心、護理之家差在哪？這篇把機構類型講清楚，附一張「詢價清單」——基本月費之外，鼻胃管、抽痰、氧氣、尿布全是另計，不會問的人一個月多花上萬塊冤枉錢。</description><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;出院之後他要去哪裡&quot;&gt;出院之後，他要去哪裡&lt;/h2&gt;
&lt;p&gt;醫院不會讓你一直住。當爸的狀況穩定下來、不再有立即的醫療處置，醫院就會開始問你：「出院後有什麼規劃？」&lt;/p&gt;
&lt;p&gt;那時候我才正視一個尷尬的事實：爸沒辦法回家。&lt;/p&gt;
&lt;p&gt;他插著鼻胃管、半身不遂、臥床、失語，需要二十四小時有人照看、定時翻身、灌食、處理排泄。我家有個剛出生的二寶、我自己還在找工作，根本不可能在家照顧一個這種程度的臥床病人。&lt;/p&gt;
&lt;p&gt;所以剩下的選項，就是「機構」。&lt;/p&gt;
&lt;p&gt;但「機構」不是一個東西。我當時以為「養老院」就是養老院，一種地方。實際去問才知道，光是收住長輩的機構就有好幾種，照護強度從輕到重排成一條光譜，價錢和能收的對象也完全不同。送錯地方，輕則被退件，重則多花一堆冤枉錢。&lt;/p&gt;
&lt;p&gt;如果你現在也正被出院日期追著跑，先不要同時想完所有問題。把事情照依賴順序拆開，會比較不容易漏掉關鍵文件或只比到表面月費：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/longterm-care-notes-05-choosing-a-facility/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這條路徑不是要你一個人做醫療判斷；病歷摘要是讓醫院與機構有共同依據，家屬要做的是把文件、現場觀察與完整價格問齊。&lt;/p&gt;
&lt;h2 id=&quot;機構有好幾種先搞懂這條光譜&quot;&gt;機構有好幾種，先搞懂這條光譜&lt;/h2&gt;
&lt;p&gt;我把當時搞懂的東西整理成一條線，從「人還很能自理」到「需要醫療等級照護」：&lt;/p&gt;






























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;類型&lt;/th&gt;&lt;th&gt;收住對象&lt;/th&gt;&lt;th&gt;醫療強度&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;安養機構&lt;/td&gt;&lt;td&gt;生活大致能自理、只是需要有人照應的長輩&lt;/td&gt;&lt;td&gt;最低&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;老人照顧中心（養護型）&lt;/td&gt;&lt;td&gt;失能、需要協助但無重大醫療需求，例如插鼻胃管、臥床&lt;/td&gt;&lt;td&gt;中&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;老人養護中心（長期照顧型）&lt;/td&gt;&lt;td&gt;失能程度更重、需要較多照顧&lt;/td&gt;&lt;td&gt;中高&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;護理之家&lt;/td&gt;&lt;td&gt;有氣切、需要頻繁抽痰、醫療需求較高的&lt;/td&gt;&lt;td&gt;最高（有護理人力）&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;爸是插鼻胃管、臥床、需要定時翻身抽痰，但沒有氣切、沒有呼吸器。這種程度落在&lt;strong&gt;養護型&lt;/strong&gt;——所以我們找的是養護中心，不是護理之家。&lt;/p&gt;
&lt;h2 id=&quot;養護中心-vs-護理之家到底差在哪&quot;&gt;養護中心 vs 護理之家，到底差在哪&lt;/h2&gt;
&lt;p&gt;這兩個最容易搞混，我特別講清楚，因為它直接影響你該往哪打電話、要付多少錢。&lt;/p&gt;
&lt;p&gt;最關鍵的差別是&lt;strong&gt;醫療強度與護理人力&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;護理之家&lt;/strong&gt;屬於醫療體系，配置有護理人員，能處理較高的醫療需求——氣切照護、頻繁抽痰、傷口換藥、複雜管路。相對地，收費通常也比較高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;養護中心&lt;/strong&gt;屬於老人福利機構（社政體系），主力是「生活照顧」——餵食、翻身、清潔、基本管路照顧。它能收插鼻胃管、臥床的長輩，但醫療等級的事情處理能力有限。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;判斷原則很簡單：&lt;strong&gt;看病人的醫療需求落在哪&lt;/strong&gt;。如果有氣切、需要頻繁抽痰或較複雜的醫療處置，護理之家比較適合；如果主要是失能、臥床、插鼻胃管這種「需要人照顧但醫療單純」的，養護中心通常就夠，而且月費相對親民。&lt;/p&gt;
&lt;p&gt;不確定的話，直接拿著病歷摘要去問機構：「我爸是這個狀況，你們收不收？」好的機構會誠實告訴你適不適合，而不是先把人收進來再說。&lt;/p&gt;
&lt;h2 id=&quot;怎麼參觀別只看裝潢&quot;&gt;怎麼參觀：別只看裝潢&lt;/h2&gt;
&lt;p&gt;決定類型之後，就是一家一家去看。我跑了幾家，學到幾個「現場才看得出來」的重點：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;看評鑑等級。&lt;/strong&gt; 這類機構政府會評鑑，分甲等、乙等之類的等級。評鑑結果通常查得到，也可以直接問機構。等級不是唯一標準，但它是一個基本門檻——而且後面講補助的時候你會發現，有些補助還會看機構是不是合格等級。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;聞味道——這個真的很重要。&lt;/strong&gt; 一走進去如果有很重的尿味、排泄物的味道蓋不掉，代表清潔和翻身換尿布的頻率可能有問題。照顧得好的機構，再多臥床長輩，味道也不會失控。這是裝潢騙不了人的地方。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;看人力比。&lt;/strong&gt; 問清楚一個照服員要顧幾位住民、夜班幾個人。比例太懸殊，代表你爸可能很久才被翻一次身、很久才被換一次尿布。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;看住民的狀態。&lt;/strong&gt; 走一圈看看現有住民：身上乾不乾淨、有沒有人理、是不是都被晾在角落。住民的樣子，就是你爸未來的樣子。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;問會客規定。&lt;/strong&gt; 能不能隨時去看、有沒有限制時段。會客越開放的機構，通常越不怕你看，這本身就是一種訊號。&lt;/p&gt;
&lt;h2 id=&quot;這篇的重點詢價別只問月費&quot;&gt;這篇的重點：詢價，別只問月費&lt;/h2&gt;
&lt;p&gt;這是我最想留給你的東西。&lt;/p&gt;
&lt;p&gt;當初我打電話問第一家，對方報「月費三萬六」，我心想「喔還好嘛」。等真的住進去、第一張帳單來，我才發現——&lt;strong&gt;月費只是入場費，後面還有一長串「另計」。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;機構的收費邏輯是「&lt;strong&gt;基本月費 ＋ 一堆加項&lt;/strong&gt;」。基本月費通常只含床位、基本生活照顧、伙食（管灌的營養品有時還另算）。但只要你爸需要任何「額外處置或耗材」，幾乎都是另外計費的。&lt;/p&gt;
&lt;p&gt;爸那時候大概的帳單是這樣（金額取整數示意，&lt;strong&gt;實際依各機構報價為準&lt;/strong&gt;）：&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;項目&lt;/th&gt;&lt;th&gt;大致費用&lt;/th&gt;&lt;th&gt;說明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;基本月費&lt;/td&gt;&lt;td&gt;約 36,500 元/月&lt;/td&gt;&lt;td&gt;首月約 39,500 元（含 3,000 元訂金，之後退抵）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;鼻胃管&lt;/td&gt;&lt;td&gt;另計&lt;/td&gt;&lt;td&gt;含管材，定期更換&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;尿管&lt;/td&gt;&lt;td&gt;另計&lt;/td&gt;&lt;td&gt;約兩週更換一次&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;抽痰&lt;/td&gt;&lt;td&gt;約 1,000 元/月&lt;/td&gt;&lt;td&gt;視頻率&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;氧氣（氧氣機）&lt;/td&gt;&lt;td&gt;約 3,000 元/月&lt;/td&gt;&lt;td&gt;後來爸狀況不需要，省下這筆&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;尿布&lt;/td&gt;&lt;td&gt;約 4,000 元/月&lt;/td&gt;&lt;td&gt;用量大&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;這些零零總總加一加，&lt;strong&gt;一個月實際多負擔大約四萬&lt;/strong&gt;。月費三萬六聽起來和最後付的四萬，中間就是這堆「另計」。&lt;/p&gt;
&lt;p&gt;關於這筆錢壓在身上一年半的感受，我在《三明治世代日記》的〈&lt;a href=&quot;https://bobochen.dev/blog/sandwich-gen-diary-17-hope-is-cruelest&quot;&gt;希望是最殘酷的&lt;/a&gt;〉寫過了，這篇不重講那種被壓垮的心情。我只想很務實地告訴你：&lt;strong&gt;這些加項，你在簽約前全部都問得到。問清楚，你就能拿同一把尺去比不同機構；不問，你就是在比一個假的數字。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以打電話問價的時候，別只問「月費多少」。把下面這些一項一項問過去：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基本月費含哪些？伙食（管灌營養品）含不含？&lt;/li&gt;
&lt;li&gt;鼻胃管、尿管的管材和更換，怎麼算？多久換一次？&lt;/li&gt;
&lt;li&gt;抽痰要不要另收？怎麼收？&lt;/li&gt;
&lt;li&gt;氧氣機租用多少？&lt;/li&gt;
&lt;li&gt;尿布是機構提供（算錢）還是家屬自備？&lt;/li&gt;
&lt;li&gt;還有沒有別的常見加項（耗材、復健、陪同就醫、代叫救護車）？&lt;/li&gt;
&lt;li&gt;押金/訂金多少、怎麼退？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把每一家的「全包試算」算出來，再來比。你會發現，月費最低的那家，加完項之後不一定最便宜。&lt;/p&gt;
&lt;h2 id=&quot;別忘了機構費用其實有補助可以申請&quot;&gt;別忘了：機構費用其實有補助可以申請&lt;/h2&gt;
&lt;p&gt;帳單嚇人，但有件事我希望當初早點知道：&lt;strong&gt;住宿式機構的費用，政府是有補助的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;像「住宿式服務機構使用者補助方案」這類方案，是針對入住合格機構的失能長輩給的；另外身心障礙者也有相應的住宿照顧費用補助。問題是——&lt;strong&gt;這塊補助是出了名的迷宮&lt;/strong&gt;：有沒有資格、補多少、機構等級夠不夠格、要附哪些文件，每個縣市、每個年度的規定都不一樣，而且常常還會把「有工作的子女收入」算進家戶所得，越努力工作反而越領不到。&lt;/p&gt;
&lt;p&gt;這篇先不展開（補助的眉角我會專門寫一篇談），這裡只給你一個動作：&lt;strong&gt;選機構的時候，順便問清楚「你們這間能不能申請住宿式機構補助？要附什麼？」&lt;/strong&gt;，再打 &lt;strong&gt;1966&lt;/strong&gt;（長照專線，前 5 分鐘免費）或各縣市的 &lt;strong&gt;1999&lt;/strong&gt; 市民專線確認你的資格。&lt;strong&gt;一切以各縣市與當年度規定為準，務必打 1966／1999 親自確認。&lt;/strong&gt; 別憑網路上一篇舊文章就斷定自己有沒有資格。&lt;/p&gt;
&lt;h2 id=&quot;入住要先準備這兩份文件&quot;&gt;入住要先準備這兩份文件&lt;/h2&gt;
&lt;p&gt;最後一個很多人卡住的地雷——入住前的文件。&lt;/p&gt;
&lt;p&gt;機構收人之前，通常會要求：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;體檢報告。&lt;/strong&gt; 確認長輩有沒有傳染病（像肺結核等）之類的問題。重點是——&lt;strong&gt;體檢報告不是當天就拿得到，通常要等好幾個工作天（我當年大概等了一週）。&lt;/strong&gt; 我當初差點卡在這裡：醫院催著出院，機構要體檢報告才能收，報告又還沒好。所以這件事一定要&lt;strong&gt;提早做&lt;/strong&gt;，別等到要出院前一天才想到。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;病歷摘要。&lt;/strong&gt; 讓機構評估你爸的狀況、能不能照顧、要怎麼安排。出院前跟醫院申請（這部分我會在出院準備那篇詳談怎麼接洽出院準備服務與社工）。帶著病歷摘要去問機構，他們才有辦法給你準確的「收不收、怎麼收費」的答案。&lt;/p&gt;
&lt;p&gt;兩份文件先備好，出院和入住才不會在最後一刻卡住。&lt;/p&gt;
&lt;h2 id=&quot;過來人提醒挑機構問價格&quot;&gt;過來人提醒：挑機構、問價格&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先分清楚機構類型&lt;/strong&gt;：安養（能自理）→ 養護中心（失能、臥床、插鼻胃管）→ 護理之家（氣切、抽痰頻繁、醫療需求高）。拿病歷摘要去問機構「我爸這狀況你們收不收」，別自己亂猜。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;參觀現場看三件事&lt;/strong&gt;：評鑑等級（甲/乙等）、&lt;strong&gt;聞味道&lt;/strong&gt;（尿味蓋不掉就扣分）、人力比（一個照服員顧幾床、夜班幾人）。順便看現有住民乾不乾淨——那就是你爸的未來。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;詢價逐項問，別只問月費。&lt;/strong&gt; 鼻胃管、尿管、抽痰、氧氣、尿布幾乎都是另計，加一加可能比月費還多上萬。把「全包試算」算出來再比，月費最低的不一定最便宜。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;問清楚補助能不能申請。&lt;/strong&gt; 住宿式機構是有費用補助的，但資格、金額、機構等級門檻各縣市與年度都不同，&lt;strong&gt;打 1966 或 1999 親自確認&lt;/strong&gt;，別憑舊資訊自己判生死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;入住前先去做體檢報告&lt;/strong&gt;，它要等好幾個工作天，醫院卻常在你還沒準備好時就催出院。一發現可能要轉機構，立刻安排體檢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;帶病歷摘要去評估。&lt;/strong&gt; 出院前向醫院申請，機構靠它判斷收不收、怎麼收費，也避免人到了才被退件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;押金/訂金要問清楚怎麼退。&lt;/strong&gt; 首月帳單通常比較高（含訂金），別被首月數字嚇到，搞懂哪部分是一次性的。&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.B-kDG2QB.webp" medium="image"/><category>長照</category><category>照顧者</category><category>護理之家</category><category>養護中心</category><category>出院準備</category><enclosure url="https://bobochen.dev/_astro/cover.B-kDG2QB.webp" length="0" type="image/png"/></item><item><title>十年前我就在玩 OpenStack：翻出 2016 年的 Horizon 簡報</title><link>https://bobochen.dev/blog/openstack-horizon-2016-flashback/</link><guid isPermaLink="true">https://bobochen.dev/blog/openstack-horizon-2016-flashback/</guid><description>在 Kubernetes 還沒一統天下、公有雲還在打地基的年代，我做了一份「Introduction OpenStack Horizon」的簡報。翻出這份 2016 年的舊筆記，順手把當年講的東西完整復刻一遍——也算是給自己的一張雲端考古學證明。</description><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;整理 Notion 舊筆記的時候，翻到一頁標題寫著 &lt;strong&gt;「Introduction OpenStack Horizon」&lt;/strong&gt; 的簡報，作者欄寫的是我自己的名字。&lt;/p&gt;
&lt;p&gt;點進去看日期——&lt;strong&gt;2016 年 8 月 5 日&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那一刻有點恍惚。2016 年，Kubernetes 才剛滿兩歲、還沒變成今天這個「容器界的作業系統」；Docker 正紅但大家還在吵它到底能不能上 production；AWS、GCP、Azure 三雄鼎立的局面也還沒完全定型。而那時候的我，已經在台上講一套叫 &lt;strong&gt;OpenStack&lt;/strong&gt; 的東西——一套讓你&lt;strong&gt;自己蓋一朵雲&lt;/strong&gt;的開源軟體。&lt;/p&gt;
&lt;p&gt;這篇就當作一個時間膠囊：把那份簡報講過的內容，用 2026 年的角度重新復刻一次。在此留個紀錄，原來我碰雲端基礎建設是在那麼古早年代。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;原始簡報還掛在 SlideShare 上：&lt;a href=&quot;https://www.slideshare.net/bobo52310/introduction-openstackhorizon&quot;&gt;Introduction OpenStack Horizon&lt;/a&gt;。介面是 2016 年的味道 XD。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div style=&quot;position: relative; width: 100%; aspect-ratio: 510 / 420; margin: 1.5rem 0;&quot;&gt;
  &lt;iframe src=&quot;https://www.slideshare.net/slideshow/embed_code/key/d3omQr1xvzWTA7&quot; title=&quot;Introduction OpenStack Horizon&quot; loading=&quot;lazy&quot; style=&quot;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 1px solid #CCC; border-radius: 8px;&quot; frameborder=&quot;0&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;p style=&quot;font-size: 0.875rem; opacity: 0.75; margin-top: 0.5rem;&quot;&gt;&lt;a href=&quot;https://www.slideshare.net/slideshow/introduction-openstackhorizon/64716425&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;introduction-openstackhorizon&lt;/a&gt;　from　&lt;a href=&quot;https://www.slideshare.net/bobo52310&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;bobo52310&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;先講脈絡openstack-是什麼&quot;&gt;先講脈絡：OpenStack 是什麼？&lt;/h2&gt;
&lt;p&gt;簡單說，&lt;strong&gt;OpenStack 是一套開源軟體，讓你在自己的機房裡蓋出一朵「私有雲」&lt;/strong&gt;——也就是 AWS EC2、GCP Compute Engine 那種「點一下就生出一台虛擬機」的能力，只是這次跑在你自己的硬體上。&lt;/p&gt;
&lt;p&gt;它不是單一程式，而是一堆服務組合起來的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nova&lt;/strong&gt; — 運算（開虛擬機）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Glance&lt;/strong&gt; — 映像檔管理（VM 的範本）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keystone&lt;/strong&gt; — 身分驗證（誰能用、能用什麼）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neutron&lt;/strong&gt; — 網路（虛擬網路、子網、路由）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cinder / Swift&lt;/strong&gt; — 區塊儲存 / 物件儲存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;問題來了：這麼多服務，每個都有自己的 API。&lt;strong&gt;一般使用者總不能每開一台機器就去敲一次 REST API 吧？&lt;/strong&gt; 得有一個圖形化、最好是網頁版的入口，讓人點一點就能管理自己的運算、儲存、網路資源。&lt;/p&gt;
&lt;p&gt;那個入口，就是 &lt;strong&gt;Horizon&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;先把各元件的關係畫在一起：使用者只面對 Horizon，但登入與每一次資源操作，最後都會落到後方各自負責一件事的服務。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/openstack-horizon-2016-flashback/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;所以 Horizon 不是把所有能力重做一次，而是把多套 API 收進同一個入口；這也解釋了為什麼後方任一 endpoint 沒接好，畫面就可能直接卡住。&lt;/p&gt;
&lt;h2 id=&quot;什麼是-horizon&quot;&gt;什麼是 Horizon？&lt;/h2&gt;
&lt;p&gt;當年簡報上我寫了三句話，到今天依然準確：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Horizon 是 OpenStack 應用程式的「門戶」。&lt;/strong&gt;
提供 Web-based 圖形化介面，方便使用者針對服務資源進行設定。
建構在 &lt;strong&gt;Django&lt;/strong&gt; 框架之上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;換句話說，Horizon 就是 OpenStack 的官方儀表板（Dashboard）。你登入後看到的那個能開機器、掛硬碟、設防火牆規則的網頁後台，就是它。&lt;/p&gt;
&lt;p&gt;而它&lt;strong&gt;自己其實什麼都不做&lt;/strong&gt;——它只是個前台。所有實際操作，都是 Horizon 透過 API 去呼叫後面的 Nova、Neutron、Glance……幫你轉達。這個設計有個好處：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Horizon 可以獨立部署。&lt;/strong&gt; 它跟其他服務之間只靠 API 溝通，所以你可以把它單獨架在一台機器上，後端服務散在別處，互不綁死。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;horizon-的三種-dashboard&quot;&gt;Horizon 的三種 Dashboard&lt;/h2&gt;
&lt;p&gt;簡報裡我特別拆解了 Horizon 的權限分層——它依角色提供三種不同的 Dashboard：&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Dashboard&lt;/th&gt;&lt;th&gt;給誰用&lt;/th&gt;&lt;th&gt;能做什麼&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;使用者 Dashboard&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;終端使用者&lt;/td&gt;&lt;td&gt;在管理員開放的權限與配額範圍內，管理自己的資源&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;權限設定 Dashboard&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;管理者&lt;/td&gt;&lt;td&gt;設定每個使用者可以操作哪些服務&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;系統 Dashboard&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;最高管理者&lt;/td&gt;&lt;td&gt;總覽整個應用程式的大小與執行狀況&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;這套「使用者 / 管理 / 系統」的分層，其實就是今天所有雲端控制台都有的 RBAC（角色權限控制）雛形——只是 2016 年的 Horizon 已經把它做成內建的三張面板了。&lt;/p&gt;
&lt;h2 id=&quot;怎麼安裝還有那個經典的坑&quot;&gt;怎麼安裝？還有那個經典的坑&lt;/h2&gt;
&lt;p&gt;簡報第 7 頁列了最小安裝需求，Horizon 要跑起來，後面至少得有這幾個服務在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nova&lt;/strong&gt;（compute、api、scheduler、network）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Glance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keystone&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neutron&lt;/strong&gt;（除非你用的是舊的 nova-network）&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;📌 &lt;strong&gt;2026 註&lt;/strong&gt;：這裡的「nova-network」選項今天已經不存在了——它就在這份簡報之後幾個月被標記淘汰，後來整個被移除，現在 Neutron 是唯一的網路方案。完整時間軸見文末〈2026 更正備註〉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然後第 8 頁，我放了一張截圖，上面只有一行字：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Oops! Unable to establish connection to keystone endpoint.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;這大概是每個第一次裝 Horizon 的人都會撞到的畫面。&lt;strong&gt;Horizon 自己沒壞，是它找不到 Keystone。&lt;/strong&gt; 因為 Horizon 開機第一件事就是去問 Keystone「這個人是誰、有沒有權限」，只要 endpoint 設錯、Keystone 沒起來、或網路不通，整個儀表板就直接給你這張臉。&lt;/p&gt;
&lt;p&gt;放這張截圖不是為了搞笑——是因為它太典型了。雲端基礎建設的痛，從來不是「功能不會用」，而是&lt;strong&gt;一堆服務之間的連線、憑證、endpoint 對不起來&lt;/strong&gt;。十年過去，這件事一點都沒變，只是場景換成了 K8s 的 Service、Ingress 跟 ServiceAccount。&lt;/p&gt;
&lt;h2 id=&quot;為什麼-horizon-選-django&quot;&gt;為什麼 Horizon 選 Django？&lt;/h2&gt;
&lt;p&gt;最後一頁，我整理了 Horizon 用 Django 當底層框架的理由：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Open Source&lt;/strong&gt; — 跟 OpenStack 整體的開源精神一致&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;i18n 多國語系&lt;/strong&gt; — 雲端產品要面對全球使用者&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MVC 架構&lt;/strong&gt; — 把介面、邏輯、資料切乾淨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;內建權限管理&lt;/strong&gt; — 剛好接上前面那套三層 Dashboard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;套件生態豐富、易擴充&lt;/strong&gt; — 而且能用 &lt;code&gt;pip&lt;/code&gt; 這個 Python 套件管理工具直接裝&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;現在回頭看，這個選擇蠻合理的：OpenStack 本身就是 Python 寫的，Horizon 用 Django（同樣是 Python 生態的當家網頁框架）等於跟整個專案的技術棧無縫對接。&lt;/p&gt;
&lt;h2 id=&quot;️-2026-更正備註哪些講法已經過時&quot;&gt;🕰️ 2026 更正備註：哪些講法已經過時&lt;/h2&gt;
&lt;p&gt;十年是一段不短的時間。把這份簡報重貼出來之前，我特地查證了一輪——有三件事，當年的講法到 2026 年已經要改口：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一、nova-network 整個消失了。&lt;/strong&gt;
安裝那頁我寫「Neutron（除非你用舊的 nova-network）」。這個「除非」今天已經不成立：nova-network 在 2016 年底的 &lt;strong&gt;Newton&lt;/strong&gt; 版（就在這份簡報之後幾個月）被標記淘汰，2018 年的 &lt;strong&gt;Rocky&lt;/strong&gt; 移除相關 API，2019 年的 &lt;strong&gt;Stein&lt;/strong&gt;（19.0.0）正式整個拔掉。今天要在 OpenStack 上做網路，&lt;strong&gt;Neutron 是唯一解&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二、版本命名把英文字母用完了。&lt;/strong&gt;
簡報裡有一頁叫「Series and milestones」，連到 OpenStack 的版本命名規則。當年的命名是照英文字母順序走（Austin、Bexar……一路到簡報當下的 Mitaka / Newton）。但字母總會用完——2022 年的 &lt;strong&gt;Zed（Z）&lt;/strong&gt; 就是字母表的終點。之後 OpenStack 改成「年份.序號」制，從 &lt;strong&gt;2023.1「Antelope」&lt;/strong&gt; 重新由 A 開始算。截至 2026 年 6 月，最新的正式版是 &lt;strong&gt;2026.1「Gazpacho」&lt;/strong&gt;（2026 年 4 月釋出），已經是第 33 個版本了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三、OpenStack 換了東家。&lt;/strong&gt;
2016 年管理 OpenStack 的是 &lt;strong&gt;OpenStack Foundation&lt;/strong&gt;。後來它在 2020 年宣布改名、2021 年正式啟用新名 &lt;strong&gt;OpenInfra Foundation&lt;/strong&gt;（Open Infrastructure，因為旗下早就不只 OpenStack）；2025 年 3 月先宣布要併入 &lt;strong&gt;Linux Foundation&lt;/strong&gt;，並於同年 7 月正式完成。也就是說，今天 Linux、OpenStack、Kubernetes 這三個專案，正式收在同一個基金會的傘下了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;岔題一句：連載這份簡報的 SlideShare 平台本身，也在 2020 年被 Scribd 收購——連放簡報的地方都換過老闆了。XD 真有趣&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;從-2026-回頭看&quot;&gt;從 2026 回頭看&lt;/h2&gt;
&lt;p&gt;把這份簡報重讀一遍，有幾個感觸：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一、OpenStack 的歷史定位。&lt;/strong&gt; 在「自建私有雲」這個需求最旺的那幾年（大約 2013–2018），OpenStack 幾乎是唯一的開源答案。電信商、銀行、政府這些不能把資料丟上公有雲的單位，都靠它撐起自己的 IaaS。後來公有雲變便宜、Kubernetes 把「容器編排」這層抽象做得更漂亮，戰場才慢慢轉移——但 OpenStack 並沒有消失，今天在電信（尤其是 5G 核網）和私有雲市場依然活著。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二、底層的道理是相通的。&lt;/strong&gt; 當年我講的那些東西——「要有一個圖形化入口」「服務之間靠 API 溝通」「身分驗證是一切的前提」「endpoint 對不起來整個就掛」——換到今天的雲原生世界，一個字都不用改。技術名詞會換，但&lt;strong&gt;基礎建設的本質不會變&lt;/strong&gt;。先搞懂過一輪 OpenStack 這種「把一朵雲拆給你看」的系統，再回頭學 Kubernetes 或任何一家公有雲，會輕鬆很多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三、留個紀錄。&lt;/strong&gt; 這才是我把它寫出來的真正原因。很多人認識我，是從這幾年的 AI、Side Project 或部落格開始的。但其實&lt;strong&gt;早在 2016 年，我就已經站在台上講雲端基礎建設了&lt;/strong&gt;。(一轉眼就十年過去了，光陰似箭呀…)&lt;/p&gt;
&lt;p&gt;十年前蓋一朵雲要懂 Nova、Glance、Keystone、Neutron 怎麼兜在一起；十年後我在玩的是 AI Agent 怎麼兜在一起。工具一直換，但那個「想把複雜系統拆開、搞懂、再講給別人聽」的人，還是同一個。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.CAos-KW3.webp" medium="image"/><category>OpenStack</category><category>Horizon</category><category>雲端</category><category>Django</category><category>IaaS</category><category>考古</category><enclosure url="https://bobochen.dev/_astro/cover.CAos-KW3.webp" length="0" type="image/png"/></item><item><title>2026 AI Coding 工具一覽表：為何我選 Claude Code 搭配 Codex</title><link>https://bobochen.dev/blog/claude-code-guide-ai-coding-landscape-2026/</link><guid isPermaLink="true">https://bobochen.dev/blog/claude-code-guide-ai-coding-landscape-2026/</guid><description>完整比較 Claude Code、OpenAI Codex CLI、Google Gemini CLI、GitHub Copilot、Cursor、Devin 六大 AI 程式設計工具。從定位差異、pricing、開源策略到 2026 年趨勢，最後分享我為什麼以 Claude Code 為主力、搭配 Codex 互補的組合。</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2026 年，如果你還在「純手工」寫程式，你的同事可能正在用 AI 以十倍速完成同樣的工作。&lt;/p&gt;
&lt;p&gt;這不是誇飾。過去一年，AI coding 工具的演化速度超乎所有人預期。2024 年我們還在用 Copilot 自動補全幾行程式碼；2025 年 AI 已經能讀懂整個 codebase、跨幾十個檔案做重構、自己開 PR 送審。到了 2026 年，六大工具各自佔據不同的生態位，形成了一個完整的市場格局。&lt;/p&gt;
&lt;p&gt;但也正因為選擇太多，很多開發者反而迷路了：Copilot、Cursor、Claude Code、Codex、Gemini CLI、Devin⋯⋯到底該用哪個？差別在哪裡？&lt;/p&gt;
&lt;p&gt;這篇文章不是要告訴你「XX 工具最好」——因為答案取決於你是誰、你怎麼工作。這篇文章要做的是：&lt;strong&gt;給你一張完整的地圖，讓你自己判斷&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;這是「Claude Code 完全指南」系列的第一篇。是的，這個系列最終會深入 Claude Code——但在那之前，我想先讓你看到完整的戰場。只有知道每個工具的定位和取捨，你才能做出有意識的選擇，而不是被行銷話術牽著走。&lt;/p&gt;
&lt;h2 id=&quot;三種-ai-coding-模式先搞清楚你在選什麼&quot;&gt;三種 AI Coding 模式：先搞清楚你在選什麼&lt;/h2&gt;
&lt;p&gt;在比較具體工具之前，你必須先理解一個更根本的問題：&lt;strong&gt;AI coding 工具有三種完全不同的運作模式&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它們不是同一類產品的不同品牌——它們根本是不同物種。&lt;/p&gt;
&lt;h3 id=&quot;模式一ide-nativeide-原生整合&quot;&gt;模式一：IDE-Native（IDE 原生整合）&lt;/h3&gt;
&lt;p&gt;代表工具：&lt;strong&gt;GitHub Copilot&lt;/strong&gt;、&lt;strong&gt;Cursor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI 直接嵌入你的編輯器裡。你在 VS Code 或 Cursor IDE 裡打字，AI 即時理解你的 context，提供自動補全、即時對話、內嵌程式碼建議。你幾乎不需要改變原本的工作習慣——AI 就在你打字的地方。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;優勢&lt;/strong&gt;：最低的學習門檻。你不需要學新工具，AI 就在你已經在用的環境裡。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限制&lt;/strong&gt;：被 IDE 綁定。你能做的事情受限於 IDE 提供的介面。離開 IDE（例如 SSH 到一台遠端伺服器），AI 就幫不上忙了。&lt;/p&gt;
&lt;h3 id=&quot;模式二terminal-agent終端機代理&quot;&gt;模式二：Terminal Agent（終端機代理）&lt;/h3&gt;
&lt;p&gt;代表工具：&lt;strong&gt;Claude Code&lt;/strong&gt;、&lt;strong&gt;OpenAI Codex CLI&lt;/strong&gt;、&lt;strong&gt;Google Gemini CLI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AI 住在你的終端機裡。你用自然語言描述任務，AI 理解你的整個專案、讀取檔案、編輯程式碼、執行 shell 指令、跑測試——一切都在 terminal 裡完成。你是指揮官，AI 是執行者。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;優勢&lt;/strong&gt;：不受 IDE 限制。SSH 能到的地方，AI 就能到。而且因為 AI 能直接執行指令，它的行動能力遠比 IDE 插件強。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限制&lt;/strong&gt;：你需要習慣 terminal 工作流。沒有漂亮的圖形介面，沒有滑鼠點點點。&lt;/p&gt;
&lt;h3 id=&quot;模式三fully-autonomous全自動代理&quot;&gt;模式三：Fully Autonomous（全自動代理）&lt;/h3&gt;
&lt;p&gt;代表工具：&lt;strong&gt;Devin&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你把一張 ticket 丟給 AI，然後去喝杯咖啡。AI 自己規劃、自己寫 code、自己測試、自己開 PR。你回來的時候，工作已經做完了（理論上）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;優勢&lt;/strong&gt;：解放雙手。適合把完整的、定義清楚的任務交出去。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限制&lt;/strong&gt;：你失去了即時控制。如果 AI 走錯方向，你可能等了半小時才發現它做的東西完全不對。而且費用通常按「任務完成單位」計價，可能很貴。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 關鍵洞察&lt;/strong&gt;：三種模式不是互斥的。2026 年的趨勢是工具開始跨界——Copilot 加了 Agent Mode、Cursor 加了雲端 Agent、Claude Code 和 Codex 加了 IDE 整合。但每個工具仍然有它最擅長的模式。選工具，先選模式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;六大工具一覽2026-年-3-月的戰場&quot;&gt;六大工具一覽：2026 年 3 月的戰場&lt;/h2&gt;
&lt;p&gt;讓我們正式進入比較。以下按工具類型分組，每個工具我會回答三個核心問題：它是什麼、它的殺手級特色是什麼、它適合誰。&lt;/p&gt;
&lt;h3 id=&quot;claude-code--推理最深的-terminal-agent&quot;&gt;Claude Code — 推理最深的 terminal agent&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;開發者&lt;/strong&gt;：Anthropic（也是 Claude 的母公司）
&lt;strong&gt;介面&lt;/strong&gt;：Terminal/CLI，另有 VS Code 擴充套件
&lt;strong&gt;模型&lt;/strong&gt;：Opus 4.6（1M context）、Sonnet 4.6
&lt;strong&gt;價格&lt;/strong&gt;：Pro $20/月、Max $100-200/月、Teams $25-150/座/月
&lt;strong&gt;開源&lt;/strong&gt;：否&lt;/p&gt;
&lt;p&gt;Claude Code 的核心定位是 &lt;strong&gt;agentic coding&lt;/strong&gt;——它不是在幫你補全程式碼，它是在理解你的整個專案、然後幫你做事。&lt;/p&gt;
&lt;p&gt;你可以這樣跟它說：「幫我把這個 API 從 REST 重構成 GraphQL，包含所有相關的 type definition、resolver 和測試」。然後 Claude Code 會自己讀懂你的 codebase 結構、找到相關的檔案、做跨檔案的修改、寫測試、跑測試確認通過。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;殺手級特色&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1M token context window&lt;/strong&gt;：目前所有 terminal agent 裡最大的推理深度。Opus 4.6 可以一次理解整個大型專案的架構&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CLAUDE.md 專案記憶系統&lt;/strong&gt;：你可以寫一份「AI 工程師操作手冊」放在專案根目錄，Claude Code 每次啟動都會讀取。它會記住你的程式碼規範、偏好的工具、常用的工作流程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;六大擴充點&lt;/strong&gt;：Skills（可重用工作流）、Hooks（生命週期事件）、Subagents（分身）、Agent Teams（多 AI 協作）、MCP（外部工具整合）、Memory（跨 session 記憶）。這讓 Claude Code 從「好用的工具」變成「可以量身打造的 AI 開發系統」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git 深度整合&lt;/strong&gt;：不只是幫你寫 commit message，而是從 branch 管理、PR 建立、code review 到 worktree 平行開發，整個 Git 工作流都能自動化&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;適合你如果&lt;/strong&gt;：你是重度 terminal 使用者、你的專案需要深度推理（不是寫 CRUD 而是做架構決策）、你想建立一套可重複使用的 AI 工作流系統。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不適合你如果&lt;/strong&gt;：你離不開 IDE 的圖形介面、你主要做簡單的前端開發、你不想花時間設定和客製化。&lt;/p&gt;
&lt;h3 id=&quot;openai-codex-cli--開源的-rust-速度&quot;&gt;OpenAI Codex CLI — 開源的 Rust 速度&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;開發者&lt;/strong&gt;：OpenAI
&lt;strong&gt;介面&lt;/strong&gt;：Terminal/CLI
&lt;strong&gt;模型&lt;/strong&gt;：GPT-5.2-Codex（針對程式碼優化的模型）
&lt;strong&gt;價格&lt;/strong&gt;：含在 ChatGPT Plus $20/月、Pro $200/月。API：codex-mini $1.50/$6.00 per M tokens
&lt;strong&gt;開源&lt;/strong&gt;：是（Apache 2.0 授權）&lt;/p&gt;
&lt;p&gt;Codex CLI 是 OpenAI 對 Claude Code 的直接回應。它在 2025 年中推出，用 Rust 從零開始構建，主打速度和開源透明度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;殺手級特色&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;開源且用 Rust 構建&lt;/strong&gt;：這是 Codex CLI 最大的差異化武器。你可以看到所有原始碼、自己編譯、甚至 fork 出自己的版本。Rust 的效能也讓它啟動速度和回應時間都很快&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原始碼留在本地&lt;/strong&gt;：只有 prompt 會傳送到 API，你的原始碼不會離開你的電腦。這對有安全疑慮的企業來說是一大賣點&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ChatGPT 訂閱即用&lt;/strong&gt;：如果你已經在付 ChatGPT Plus，Codex CLI 不需要額外付費。對 OpenAI 生態系的使用者來說零成本切入&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;適合你如果&lt;/strong&gt;：你重視開源和透明度、你已經在 OpenAI 生態系裡、你關心程式碼隱私。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不適合你如果&lt;/strong&gt;：你需要超大的 context window（GPT-5.2-Codex 的 context 小於 Claude 的 1M）、你需要深度的擴充機制（Skills、Hooks 等 Claude Code 的進階功能 Codex CLI 還沒有）。&lt;/p&gt;
&lt;h3 id=&quot;google-gemini-cli--最慷慨的免費方案&quot;&gt;Google Gemini CLI — 最慷慨的免費方案&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;開發者&lt;/strong&gt;：Google
&lt;strong&gt;介面&lt;/strong&gt;：Terminal/CLI
&lt;strong&gt;模型&lt;/strong&gt;：Gemini 2.5 Pro（1M context）
&lt;strong&gt;價格&lt;/strong&gt;：&lt;strong&gt;免費方案每天 1,000 次請求、每分鐘 60 次&lt;/strong&gt;。付費走 Google Cloud 定價
&lt;strong&gt;開源&lt;/strong&gt;：是（Apache 2.0 授權）&lt;/p&gt;
&lt;p&gt;Google 在 2025 年 6 月推出 Gemini CLI，策略非常清楚：用免費方案搶佔市場。每天 1,000 次免費請求，這在所有 AI coding 工具裡是最慷慨的——甚至比很多付費方案的額度還高。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;殺手級特色&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;業界最慷慨的免費方案&lt;/strong&gt;：1,000 請求/天、60 請求/分鐘，免費。對學生、獨立開發者、或只是想試水溫的人來說，這是零風險的入門選擇&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Search 內建&lt;/strong&gt;：Gemini CLI 可以直接用 Google Search 搜尋即時資訊。當你需要查 API 文件、看 Stack Overflow 的解法，它不需要你另外設定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1M token context&lt;/strong&gt;：跟 Claude Code 一樣有百萬等級的 context window，能理解大型專案&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 支援&lt;/strong&gt;：跟 Claude Code 一樣支援 Model Context Protocol，可以連接外部工具&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;適合你如果&lt;/strong&gt;：你想零成本試用 AI coding、你是 Google Cloud 使用者、你需要大量的日常 AI 輔助但預算有限。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不適合你如果&lt;/strong&gt;：你需要可客製化的工作流系統（Gemini CLI 的擴充機制不如 Claude Code 成熟）、你對 Google 的資料使用政策有疑慮。&lt;/p&gt;
&lt;h3 id=&quot;github-copilot--最多人用的全方位選手&quot;&gt;GitHub Copilot — 最多人用的全方位選手&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;開發者&lt;/strong&gt;：GitHub（Microsoft）
&lt;strong&gt;介面&lt;/strong&gt;：IDE 為主（VS Code、JetBrains、Neovim）、CLI（2026/2 GA）、GitHub.com 網頁
&lt;strong&gt;模型&lt;/strong&gt;：多模型（GPT、Claude、Gemini 都可選）
&lt;strong&gt;價格&lt;/strong&gt;：Free（2,000 補全 + 50 次進階請求/月）、Pro $10/月、Pro+ $39/月、Business $19/座/月
&lt;strong&gt;開源&lt;/strong&gt;：否&lt;/p&gt;
&lt;p&gt;Copilot 是 AI coding 工具的開山始祖（2021 年推出），也是目前使用者最多的。它的策略已經從「自動補全」演化成「多模型 + 多模式 + 多平台」的全方位生態系。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;殺手級特色&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多模型支援&lt;/strong&gt;：你可以在 Copilot 裡選用 GPT、Claude、Gemini 等不同模型。這意味著你不需要被綁在單一模型上&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最深的 GitHub 整合&lt;/strong&gt;：這是其他工具不可能複製的優勢。Copilot 可以直接操作 GitHub——自動開 PR、做 code review、觸發 Actions、跟 Issues 連動。如果你的工作流離不開 GitHub，這是最無縫的選擇&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Copilot Coding Agent&lt;/strong&gt;：2026 年推出的殺手功能。你指派一個 GitHub Issue 給 Copilot，它會自己建 branch、寫 code、開 PR。你只需要 review 和 merge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$10/月的 Pro 方案&lt;/strong&gt;：在所有付費 AI coding 工具裡，這是最便宜的。而免費方案（2,000 次補全/月）也足以讓你感受到 AI coding 的威力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;適合你如果&lt;/strong&gt;：你想要最低門檻的 AI coding 體驗、你的工作流深度依賴 GitHub、你想要多模型選擇的彈性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不適合你如果&lt;/strong&gt;：你是重度 terminal 使用者、你需要深度的專案客製化（CLAUDE.md 式的記憶系統）、你追求單一模型的最深推理能力。&lt;/p&gt;
&lt;h3 id=&quot;cursor--ai-native-的-ide-體驗&quot;&gt;Cursor — AI-Native 的 IDE 體驗&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;開發者&lt;/strong&gt;：Anysphere
&lt;strong&gt;介面&lt;/strong&gt;：桌面 IDE（VS Code fork）
&lt;strong&gt;模型&lt;/strong&gt;：多模型（Claude、GPT 等）
&lt;strong&gt;價格&lt;/strong&gt;：Hobby（免費，有限制）、Pro $20/月、Business $40/座/月
&lt;strong&gt;開源&lt;/strong&gt;：否&lt;/p&gt;
&lt;p&gt;Cursor 的思路跟其他工具不同——它不是在現有 IDE 上加 AI，而是從頭打造一個 &lt;strong&gt;AI 原生的 IDE&lt;/strong&gt;。每一個介面、每一個互動都是圍繞「跟 AI 一起寫程式」來設計的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;殺手級特色&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;平行雲端 Agent&lt;/strong&gt;：Cursor 2.0 最強的功能。你可以同時啟動最多 8 個 Agent 在雲端平行工作——一個寫前端、一個寫後端、一個寫測試。這是其他 IDE 工具做不到的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automations（自動化觸發）&lt;/strong&gt;：你可以設定「當某個事件發生時，自動執行某個 Agent」。例如每次有人 push 到 main branch，自動跑 code review agent&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;視覺化 diff&lt;/strong&gt;：Cursor 的 diff 顯示方式是專門為 AI 生成的程式碼設計的。你可以一眼看出 AI 改了什麼、要不要接受&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JetBrains 整合&lt;/strong&gt;：2026 年 3 月，Cursor 透過 ACP 協議跟 JetBrains IDE 整合。如果你是 IntelliJ / PyCharm 使用者，也能享受 Cursor 的 AI 能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;適合你如果&lt;/strong&gt;：你喜歡完整的 IDE 體驗、你需要平行 Agent 加速大型任務、你想要「看得到」的 AI 互動（視覺化 diff、內嵌對話）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不適合你如果&lt;/strong&gt;：你不想被特定 IDE 綁住、你習慣 terminal 工作流、你的開發環境是遠端伺服器。&lt;/p&gt;
&lt;h3 id=&quot;devin--全自動的-ai-軟體工程師&quot;&gt;Devin — 全自動的 AI 軟體工程師&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;開發者&lt;/strong&gt;：Cognition（2025 年收購了 Windsurf）
&lt;strong&gt;介面&lt;/strong&gt;：網頁（雲端 IDE）
&lt;strong&gt;模型&lt;/strong&gt;：自研模型
&lt;strong&gt;價格&lt;/strong&gt;：Core $20/月 + $2.25/ACU（按完成量計費）、Teams $500/月（含 250 ACU）
&lt;strong&gt;開源&lt;/strong&gt;：否&lt;/p&gt;
&lt;p&gt;Devin 跟前面五個工具的定位完全不同。它不是你的「助手」——它是你的「AI 同事」。你丟一張 ticket 給它，它自己規劃、自己寫 code、自己測試、自己開 PR。你不需要坐在旁邊看，做完了它會通知你。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;殺手級特色&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全自動執行&lt;/strong&gt;：你指派任務後可以去做其他事情。Devin 在雲端沙盒裡自己完成所有工作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;平行 Devin&lt;/strong&gt;：你可以同時跑多個 Devin 處理不同的任務。等於同時有好幾個 AI 工程師在幹活&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Devin Wiki&lt;/strong&gt;：自動為你的 repo 生成文件。Devin 會分析你的程式碼結構，自動產出架構文件&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interactive Planning&lt;/strong&gt;：在 Devin 開始執行之前，它會先告訴你它打算怎麼做。你可以調整計畫，確認方向對了再讓它動手&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;適合你如果&lt;/strong&gt;：你有大量定義清楚的開發任務想要並行處理、你是團隊主管想要擴充產能、你願意接受「交出去就別管了」的工作模式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不適合你如果&lt;/strong&gt;：你需要即時互動和精細控制、你的任務需求模糊需要持續溝通、你的程式碼有高度安全性要求（Devin 在雲端運行）。&lt;/p&gt;
&lt;h2 id=&quot;完整比較表&quot;&gt;完整比較表&lt;/h2&gt;








































































































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Claude Code&lt;/th&gt;&lt;th&gt;Codex CLI&lt;/th&gt;&lt;th&gt;Gemini CLI&lt;/th&gt;&lt;th&gt;Copilot&lt;/th&gt;&lt;th&gt;Cursor&lt;/th&gt;&lt;th&gt;Devin&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;類型&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Terminal Agent&lt;/td&gt;&lt;td&gt;Terminal Agent&lt;/td&gt;&lt;td&gt;Terminal Agent&lt;/td&gt;&lt;td&gt;IDE + Agent&lt;/td&gt;&lt;td&gt;AI-Native IDE&lt;/td&gt;&lt;td&gt;全自動 Agent&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;模型&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Opus 4.6 / Sonnet 4.6&lt;/td&gt;&lt;td&gt;GPT-5.2-Codex&lt;/td&gt;&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;&lt;td&gt;多模型&lt;/td&gt;&lt;td&gt;多模型&lt;/td&gt;&lt;td&gt;自研&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;1M tokens&lt;/td&gt;&lt;td&gt;較小&lt;/td&gt;&lt;td&gt;1M tokens&lt;/td&gt;&lt;td&gt;依模型&lt;/td&gt;&lt;td&gt;依模型&lt;/td&gt;&lt;td&gt;整個 repo&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;開源&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;✅ Apache 2.0&lt;/td&gt;&lt;td&gt;✅ Apache 2.0&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;免費方案&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;有限&lt;/td&gt;&lt;td&gt;ChatGPT Free&lt;/td&gt;&lt;td&gt;&lt;strong&gt;1,000 req/天&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;2,000 補全/月&lt;/td&gt;&lt;td&gt;有限&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;付費起步&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;$20/月&lt;/td&gt;&lt;td&gt;$20/月&lt;/td&gt;&lt;td&gt;GCP 定價&lt;/td&gt;&lt;td&gt;&lt;strong&gt;$10/月&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;$20/月&lt;/td&gt;&lt;td&gt;$20/月 + ACU&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;最強項&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;推理深度 + 擴充系統&lt;/td&gt;&lt;td&gt;開源 + Rust 速度&lt;/td&gt;&lt;td&gt;免費 + Google Search&lt;/td&gt;&lt;td&gt;GitHub 整合 + 多模型&lt;/td&gt;&lt;td&gt;視覺化 + 平行 Agent&lt;/td&gt;&lt;td&gt;全自動 + 並行&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;IDE 整合&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;VS Code 擴充&lt;/td&gt;&lt;td&gt;有限&lt;/td&gt;&lt;td&gt;有限&lt;/td&gt;&lt;td&gt;&lt;strong&gt;原生&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;IDE 本身&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;雲端 IDE&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Terminal&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;原生&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;原生&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;原生&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;CLI (GA 2026/2)&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;擴充性&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐⭐&lt;/td&gt;&lt;td&gt;⭐⭐&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;表格裡的價格是會過期的那一欄，所以把兩家的官方定價頁附上，你自己核對當下的數字：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Copilot 這張有一行值得單獨拿出來講：Pro 方案寫著 &lt;strong&gt;「Access to 3rd party agents (Claude Code and Codex)」&lt;/strong&gt;——也就是說，這篇在比較的幾個工具，現在有一部分可以直接掛在 Copilot 訂閱底下用。「選一個」這個問題的前提，已經比我寫這篇的時候更模糊了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Cursor 的免費 Hobby 方案值得注意的不是「免費」，是那句 &lt;strong&gt;「有限的代理請求額度」&lt;/strong&gt;——它免費的是編輯器，不是 agent。真正要用 agent 跑事情，起步就是 $20。&lt;/p&gt;
&lt;h2 id=&quot;2026-年五大趨勢你該知道的產業動向&quot;&gt;2026 年五大趨勢：你該知道的產業動向&lt;/h2&gt;
&lt;p&gt;比較完個別工具，讓我們退一步看看整個產業的大趨勢。這些趨勢會影響你的選擇策略。&lt;/p&gt;
&lt;h3 id=&quot;趨勢一terminal-first-成為標配&quot;&gt;趨勢一：Terminal-First 成為標配&lt;/h3&gt;
&lt;p&gt;2024 年，terminal AI agent 還是小眾工具。到了 2026 年，每一個大廠都推出了自己的 CLI agent：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anthropic 有 Claude Code（2024 年底）&lt;/li&gt;
&lt;li&gt;OpenAI 有 Codex CLI（2025 年中）&lt;/li&gt;
&lt;li&gt;Google 有 Gemini CLI（2025 年 6 月）&lt;/li&gt;
&lt;li&gt;GitHub 的 Copilot CLI 在 2026 年 2 月正式 GA&lt;/li&gt;
&lt;li&gt;JetBrains 也在 2026 年 3 月推出 Junie CLI beta&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;這意味著什麼&lt;/strong&gt;：Terminal 不再是 niche。如果你還沒試過 terminal AI agent，2026 年是最好的入門時機——選擇最多、免費方案最豐富。&lt;/p&gt;
&lt;h3 id=&quot;趨勢二開源成為競爭武器&quot;&gt;趨勢二：開源成為競爭武器&lt;/h3&gt;
&lt;p&gt;OpenAI 和 Google 都把自家的 CLI agent 開源了——這在大型 AI 公司裡是很不尋常的舉動。它們的模型是閉源的，但 CLI 工具是開源的。&lt;/p&gt;
&lt;p&gt;這背後的策略很清楚：&lt;strong&gt;用開源搶佔開發者的桌面&lt;/strong&gt;。如果開發者習慣了 Codex CLI 的操作方式，就更可能持續使用（和付費）OpenAI 的 API。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這意味著什麼&lt;/strong&gt;：如果開源對你很重要（例如你在受監管的產業、或你需要自己 host），Codex CLI 和 Gemini CLI 是目前最好的選擇。&lt;/p&gt;
&lt;h3 id=&quot;趨勢三20月的價格帶收斂&quot;&gt;趨勢三：$20/月的價格帶收斂&lt;/h3&gt;
&lt;p&gt;幾乎所有個人付費方案都落在 $20/月上下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Claude Code Pro：$20/月&lt;/li&gt;
&lt;li&gt;Codex（ChatGPT Plus）：$20/月&lt;/li&gt;
&lt;li&gt;Cursor Pro：$20/月&lt;/li&gt;
&lt;li&gt;Devin Core：$20/月&lt;/li&gt;
&lt;li&gt;Copilot Pro 是例外，只要 $10/月&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;這意味著什麼&lt;/strong&gt;：價格已經不是主要的選擇因素了。在同樣的價位下，真正的差異在功能、生態系和你的工作模式。&lt;/p&gt;
&lt;h3 id=&quot;趨勢四多模型成為預設&quot;&gt;趨勢四：多模型成為預設&lt;/h3&gt;
&lt;p&gt;GitHub Copilot、Cursor、JetBrains Junie 都支援多個模型提供者。你可以在同一個工具裡切換 GPT、Claude、Gemini。&lt;/p&gt;
&lt;p&gt;而 Claude Code 和 Codex CLI 則是「單一模型、極致深度」的策略——它們只支援自家模型，但深度整合到極致。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這意味著什麼&lt;/strong&gt;：這是「平台 vs 產品」的策略分歧。你偏好彈性（多模型），還是深度（單一模型極致優化）？沒有對錯，但你需要知道自己在選什麼。&lt;/p&gt;
&lt;h3 id=&quot;趨勢五收購大戰重塑版圖&quot;&gt;趨勢五：收購大戰重塑版圖&lt;/h3&gt;
&lt;p&gt;2025 年的收購案讓整個市場大洗牌：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Anthropic 收購 Bun&lt;/strong&gt;（2025 年 12 月）：Claude Code 現在有了自己的 JavaScript runtime。這暗示未來可能有更深的開發工具整合&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenAI 嘗試收購 Windsurf&lt;/strong&gt;（$30 億，失敗）：Microsoft 擋了這筆交易&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google 挖走 Windsurf 團隊&lt;/strong&gt;（$24 億授權協議）：CEO 和共同創辦人加入 DeepMind&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cognition 收購 Windsurf IP&lt;/strong&gt;：Devin 的開發者拿到了 Windsurf 的技術和品牌&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四則新聞裡有三則圍著同一家公司打轉。Windsurf 這個標的，OpenAI 沒買成、團隊被 Google 挖走、IP 落到 Cognition 手上：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/claude-code-guide-ai-coding-landscape-2026/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;這意味著什麼&lt;/strong&gt;：AI coding 太重要了，大廠不會放手。Windsurf 的故事告訴我們：小型獨立工具隨時可能被收購或消失。如果你在乎長期穩定性，選大廠的產品會比較安全。&lt;/p&gt;
&lt;h2 id=&quot;怎麼選四個決策維度&quot;&gt;怎麼選？四個決策維度&lt;/h2&gt;
&lt;p&gt;資訊量很大，讓我幫你收斂。根據你的情況，有四個維度可以幫你做決定：&lt;/p&gt;
&lt;h3 id=&quot;維度一你的工作介面偏好&quot;&gt;維度一：你的工作介面偏好&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;你主要在哪裡寫程式？

Terminal / Vim / Neovim    → Claude Code、Codex CLI、Gemini CLI
VS Code                    → Copilot、Cursor、Claude Code（VS Code 擴充）
JetBrains IDE              → Copilot、Cursor（ACP）、Junie
不想裝任何東西              → Devin（純網頁）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;維度二你的預算&quot;&gt;維度二：你的預算&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$0（零預算）         → Gemini CLI（1,000 req/天免費）
$10/月（最小投資）   → GitHub Copilot Pro
$20/月（標準投資）   → Claude Code / Codex CLI / Cursor（看偏好）
$100+/月（重度使用） → Claude Code Max / Cursor Business / Devin Teams
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;維度三你的任務類型&quot;&gt;維度三：你的任務類型&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;日常寫 code（CRUD、小功能）    → Copilot（最順手）
大型重構 / 架構決策            → Claude Code（推理最深）
探索性開發（不確定方向）       → Claude Code / Cursor（即時互動）
批量任務（10 張 tickets）      → Devin（丟出去就好）
學習新技術 / 看文件            → Gemini CLI（免費 + Google Search）
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;維度四你對客製化的需求&quot;&gt;維度四：你對客製化的需求&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;開箱即用就好      → Copilot、Gemini CLI
想做一些設定      → Cursor、Codex CLI
想打造完整系統    → Claude Code（Skills + Hooks + MCP + Agent Teams）
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;我為什麼選-claude-code-搭配-codex&quot;&gt;我為什麼選 Claude Code 搭配 Codex&lt;/h2&gt;
&lt;p&gt;身為一個同時使用過以上所有工具的開發者，讓我坦白分享我的選擇和理由。&lt;/p&gt;
&lt;p&gt;我的主力是 &lt;strong&gt;Claude Code&lt;/strong&gt;，但我不是單押一個工具——我把它&lt;strong&gt;搭配 Codex&lt;/strong&gt; 一起用。&lt;/p&gt;
&lt;p&gt;先說主力。我選 Claude Code，不是因為它在每個面向都是最好的——事實上，Copilot 的 IDE 整合更順手、Gemini CLI 的免費方案更慷慨，連 Codex 的開源都比它透明。&lt;/p&gt;
&lt;p&gt;我選它，是因為它的&lt;strong&gt;擴充深度無與倫比&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;經過一年多的使用，我已經建立了 60 多個 Skills（可重用工作流）、8 個 Hooks（自動化觸發器）、連接了 10 多個 MCP server（外部工具整合）。我的 CLAUDE.md 超過 300 行，詳細定義了我的開發規範、commit 習慣、除錯策略、收工流程。&lt;/p&gt;
&lt;p&gt;換句話說，我不是在「用一個工具」——我是在經營一個&lt;strong&gt;為我量身打造的 AI 開發系統&lt;/strong&gt;。它記得我的偏好、遵循我的規範、在我犯錯之前就提醒我。這是其他工具的擴充機制做不到的。&lt;/p&gt;
&lt;p&gt;那為什麼還要&lt;strong&gt;搭配 Codex&lt;/strong&gt;？&lt;/p&gt;
&lt;p&gt;因為&lt;strong&gt;第二個模型，就是第二種觀點&lt;/strong&gt;。Claude Code 和 Codex 背後是兩個頂尖、但盲點不一樣的模型。當 Claude 給的方案我不太確定，或我想交叉驗證一個架構決策時，我會把同一個問題丟給 Codex（GPT-5.2-Codex）換個角度看——兩邊都說 OK，我才放心動手。&lt;/p&gt;
&lt;p&gt;實務上，這個組合還有三個好處：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;不中斷&lt;/strong&gt;：Claude Code 撞到用量上限時，Codex 直接接手，工作流不卡住。這篇文章的封面圖就是活生生的例子——Claude Code 喊停後，我用 Codex 把圖生出來，接著再交回 Claude Code 收尾。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;互補強項&lt;/strong&gt;：需要把原始碼留在本地、或想要開源透明度的敏感任務，我走 Codex；需要深度推理、跨大量檔案的重構，我交給 Claude Code。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能協作&lt;/strong&gt;：Claude Code 可以直接從 shell 呼叫 &lt;code&gt;codex&lt;/code&gt;，等於讓我的主力 agent 隨時調用第二個模型當外援，而不是我自己在兩個視窗之間複製貼上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把這三件事接起來，一個任務在我手上的實際走法是這樣：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/claude-code-guide-ai-coding-landscape-2026/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;主力 + 互補，比單押一個工具更穩。&lt;/p&gt;
&lt;p&gt;但我必須誠實地說：&lt;strong&gt;這套組合不是每個人一開始就需要的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果你是 AI coding 的新手，從 &lt;strong&gt;Copilot&lt;/strong&gt;（$10/月）或 &lt;strong&gt;Gemini CLI&lt;/strong&gt;（免費）開始才是對的。先用一個工具把工作模式跑順，等你確定自己需要什麼程度的客製化、也開始感覺到「單一模型的盲點」，再來決定要不要立一個主力、加一把第二刀。&lt;/p&gt;
&lt;p&gt;一步步來。&lt;/p&gt;
&lt;h2 id=&quot;這個系列接下來會帶你做什麼&quot;&gt;這個系列接下來會帶你做什麼&lt;/h2&gt;
&lt;p&gt;「Claude Code 完全指南」是一個 18 篇的系列，從入門到精通，帶你完整掌握 Claude Code 的每一個面向：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part I：基礎入門（第 1-5 篇）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;打好地基。認識 AI coding 的全景（就是你正在讀的這篇），理解 Claude Code 的核心思維，完成安裝和第一次對話，搞懂權限模型和基本操作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part II：六大擴充點（第 6-11 篇）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;這是 Claude Code 跟其他工具拉開差距的地方。CLAUDE.md 專案記憶、Skills 工作流、Hooks 自動化、Subagents 分身術、Agent Teams 多 AI 協作、MCP 外部整合——每一個都是一篇完整的深度教學。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part III：進階工作流（第 12-15 篇）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;把 Claude Code 用在真實的工作場景：Git 工作流自動化、CI/CD 整合、除錯策略、大型專案管理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Part IV：心法與未來（第 16-18 篇）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;從個人到團隊的 CLAUDE.md 架構設計、把你的所有客製化整合成一個完整的 AI 開發系統、以及 Claude Code 的未來展望。&lt;/p&gt;
&lt;p&gt;不管你現在用什麼工具，Part I 的前五篇都值得你讀——因為 AI coding 的核心概念是通用的。&lt;/p&gt;
&lt;h2 id=&quot;下一步&quot;&gt;下一步&lt;/h2&gt;
&lt;p&gt;現在你已經看到了 2026 年 AI coding 的完整地圖。六大工具各有定位、各有所長，沒有一個「最好的」——只有最適合你的。&lt;/p&gt;
&lt;p&gt;下一篇，我們正式進入 Claude Code 的世界：&lt;strong&gt;它到底是什麼、它跟其他 AI coding 工具的根本差異在哪裡、以及它的六大擴充點如何讓它從「工具」變成「系統」&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;準備好了就繼續吧。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.kisg4T0w.webp" medium="image"/><category>Claude Code</category><category>AI Coding</category><category>Codex</category><category>Gemini CLI</category><category>Copilot</category><category>Cursor</category><category>Devin</category><category>工具比較</category><enclosure url="https://bobochen.dev/_astro/cover.kisg4T0w.webp" length="0" type="image/png"/></item><item><title>一個工程師創辦人的真實成本清單：CityTasker 教我的 4 件事</title><link>https://bobochen.dev/blog/citytasker-engineer-founder-real-costs/</link><guid isPermaLink="true">https://bobochen.dev/blog/citytasker-engineer-founder-real-costs/</guid><description>工程師創業，最容易低估的從來不是技術，而是看不見的成本——燒錢的速度、沒人教你的法規、聚焦的代價，還有那個致命錯覺：寫得出來的東西，最不值錢。這是 CityTasker 用兩年燒完的錢，幫我列出的成本清單。</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2012 到 2014 年，我和朋友做了一個叫 CityTasker 的任務媒合平台——口號是「整個城市都有我的好幫手」，你有跑腿、代買、家事這類懶得做或做不來的事，就丟上平台，找附近的人接走。我那時候是共同創辦人兼全端工程師，前端、後端、雙平台 App 一個人扛。&lt;/p&gt;
&lt;p&gt;我以為最難的是把它做出來。&lt;/p&gt;
&lt;p&gt;後來才知道，把東西做出來，是這場創業裡最簡單、也最不值錢的一件事。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;這就是當年我們做出來的 CityTasker。把它寫出來，其實是整件事裡最簡單的一步。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;下面這份清單，是 CityTasker 用兩年和我口袋裡的錢，幫我列出來的。寫給每一個正站在懸崖邊、猶豫要不要跳下去的工程師。&lt;/p&gt;
&lt;p&gt;如果把四個教訓改成進場順序，真正該做的事情幾乎都發生在打開編輯器之前：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/citytasker-engineer-founder-real-costs/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;你以為錢花在伺服器其實是花在時間&quot;&gt;你以為錢花在伺服器，其實是花在時間&lt;/h2&gt;
&lt;p&gt;工程師看成本，習慣看帳單：AWS 多少、GCP 多少、簡訊一封幾毛。這些我算得很精。&lt;/p&gt;
&lt;p&gt;但真正燒掉資金的，不是機器，是時間。是你還沒搞清楚方向的每一個月，房租照付、人照養、自己也要吃飯。我們最後收攤，不是被某一筆大開銷壓垮，而是「還沒找到正確方向之前，就先坐吃山空了」。錢有跑道（runway），方向沒跑道——你以為自己在加速，其實只是在燒油空轉。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提醒&lt;/strong&gt;：算 runway 的時候，把「找到對的方向」當成最大的一筆隱形支出。問自己：如果接下來六個月方向都是錯的，我撐得住嗎？撐不住，就先別跳。&lt;/p&gt;
&lt;h2 id=&quot;你寫得出平台但沒人教你法律&quot;&gt;你寫得出平台，但沒人教你法律&lt;/h2&gt;
&lt;p&gt;CityTasker 上線後沒多久，我們收到一個完全沒料到的麻煩：勞動局以「非法人力仲介」要開罰。&lt;/p&gt;
&lt;p&gt;我當時的反應是——蛤？我們只是做了一個媒合資訊的網站啊。但在法規眼裡，你撮合了「人」去「做事」並從中產生關係，那條線就踩到了。這是一道工程師完全沒有雷達掃到的牆。你的 code review 不會幫你 review 法律，你的測試覆蓋率也涵蓋不到主管機關。&lt;/p&gt;
&lt;p&gt;更扎心的是，把我們捅到主管機關面前的，不是哪個官員自己上網發現——是同業。我們等於動了傳統人力仲介的乳酪，有人當面嗆聲，也有人直接拿著我們的網站去檢舉。那時我才懂：你以為自己在「做一個平台」，但在既有玩家眼裡，你是個闖進別人地盤、還不懂規矩的外人。法規那條看不見的線，常常是被「不希望你存在的人」幫你用力劃出來的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提醒&lt;/strong&gt;：你的產品只要碰到「人、錢、健康、勞動」其中任何一個，先去問律師，別先寫 code。法規不是寫完上線後才補的功能，它是你能不能上線的前提——而且別忘了，最先去翻法條找你麻煩的，往往是被你動到的同業。&lt;/p&gt;
&lt;h2 id=&quot;想服務所有人最後誰都沒服務好&quot;&gt;想服務所有人，最後誰都沒服務好&lt;/h2&gt;
&lt;p&gt;我們的定位很「大」。一邊想接小 B——行銷公司、公關公司、百貨賣場、活動展覽、研究調查；一邊又想做個人需求——代排、代買、跑腿、家事。聽起來市場很大，對吧？&lt;/p&gt;
&lt;p&gt;實際上，這代表我們的首頁要同時說服一個百貨採購和一個想找人代排隊的學生。代表每一個功能都得兼顧兩種完全不同的人。代表我們的行銷預算（本來就少得可憐）被切成好幾份，每一份都不夠用。用戶成長一直起不來，現在回頭看，不是行銷沒做好，是我們從沒讓任何一種人覺得「這就是為我做的」。&lt;/p&gt;
&lt;p&gt;更現實的是，我們媒合的那些事——搬家、代排、展場銷售人員——產值本來就低。B 端的公司要這些臨時人力，一單只肯付我們 50 元。50 元。就算用戶成長真的衝起來，把每一單的數字攤開，這個模式從一開始就撐不起一間公司。問題不只是「想服務所有人」，是我們挑的這幾個池子本身都太淺——選錯了池子，再努力划水也游不出去。&lt;/p&gt;
&lt;p&gt;聚焦，在當時的我眼裡是一個「策略選擇」——好像聚焦了就放棄了其他可能。但真相是：聚焦是生存問題，不是選擇題。它不只決定你能不能說服一種人，也決定你撈到的每一單，到底值不值得你做。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提醒&lt;/strong&gt;：開站第一天就回答得出「我先為哪一種人、解決哪一個具體場景」，而且要窄到讓你有點害怕。怕，通常代表你終於夠聚焦了。&lt;/p&gt;
&lt;h2 id=&quot;能寫出來不等於有人要用&quot;&gt;「能寫出來」不等於「有人要用」&lt;/h2&gt;
&lt;p&gt;這是工程師最致命的錯覺，我也栽了。&lt;/p&gt;
&lt;p&gt;開站那天，我盯著後台一直按 F5——一天 837 個人註冊、最高 50 人同時在線。那一刻我和夥伴真的以為要成功了。但 837 個註冊，從來不等於 837 個「明天還會再回來」的人。我把「做得出來」和「有人持續要用」當成同一件事，這中間其實隔著一整個我沒搞懂的世界：需求是不是真的、頻率夠不夠、他願不願意付錢、付了之後會不會再來。&lt;/p&gt;
&lt;p&gt;我花了大把力氣把功能刻得又快又漂亮，卻很少停下來問：這個功能，到底有沒有人在等？寫得出來，是這整件事裡最廉價的能力。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提醒&lt;/strong&gt;：在你打開編輯器之前，先想辦法證明「有人要用」——哪怕只是用一張表單、一個 LINE 群、十通電話。能寫出來不是你的護城河，搞懂有沒有人要才是。&lt;/p&gt;
&lt;h2 id=&quot;所以呢別創業嗎&quot;&gt;所以呢，別創業嗎？&lt;/h2&gt;
&lt;p&gt;不是。&lt;/p&gt;
&lt;p&gt;CityTasker 收攤後，我們各自先回去上班，有人轉去了旅遊產業，我則一路做到後來的技術負責人。我不後悔跳下去——那兩年讓我真正理解了 0 到 1 是什麼，這是再多薪水都買不到的學費。&lt;/p&gt;
&lt;p&gt;我想說的只是：跳，但張開眼睛再跳。技術從來不是創業最高的那道牆，這份清單上的每一項才是。看清楚它們，不會讓你變膽小，只會讓你跳得比當年的我聰明一點。&lt;/p&gt;
&lt;p&gt;至於那句一直在我心裡的「我們不是那 1%」——那是這個系列收尾時，我才想好好說清楚的事。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.CZXJ8wo9.webp" medium="image"/><category>創業</category><category>AppWorks</category><category>CityTasker</category><category>新創</category><category>職涯反思</category><category>工程師創業</category><enclosure url="https://bobochen.dev/_astro/cover.CZXJ8wo9.webp" length="0" type="image/png"/></item><item><title>整個城市都有我的好幫手：CityTasker 開站那天，我們真的以為要成功了</title><link>https://bobochen.dev/blog/citytasker-launch-day-story/</link><guid isPermaLink="true">https://bobochen.dev/blog/citytasker-launch-day-story/</guid><description>2013 年 8 月 7 日，CityTasker 開站。我盯著後台一直按 F5，一天 837 個人註冊、最高 50 人同時在線、54 封簡訊發出去——那一刻，我跟夥伴真的以為要成功了。這是一個入選 AppWorks 第七屆、最後燒完錢收攤的創業故事的開場。</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;開站那天我一直在重新整理後台&quot;&gt;開站那天，我一直在重新整理後台&lt;/h2&gt;
&lt;p&gt;2013 年 8 月 7 日，我幾乎沒離開過電腦。&lt;/p&gt;
&lt;p&gt;不是在寫程式——程式那天難得沒出包——而是一直按 F5。後台的數字每按一次就往上跳一格：註冊會員 57、94、101……到了傍晚，停在 837。同一時間最高有 50 個人在線上瀏覽，系統發出去 54 封簡訊通知，單日流量衝到 347MB。&lt;/p&gt;
&lt;p&gt;對今天動不動百萬日活的產品來說，這些數字小得可笑。但那天傍晚，我跟共同創辦人 JEJ 盯著螢幕，真的以為——我們要成功了。&lt;/p&gt;
&lt;p&gt;那個產品叫 CityTasker。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;開站當天的流量後台截圖。我一直留著它——&lt;code&gt;citytasker.tw&lt;/code&gt; 那天跑了 346.95 MB，是這個小東西活著的證據。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;一句話的傻勁&quot;&gt;一句話的傻勁&lt;/h2&gt;
&lt;p&gt;CityTasker 的構想很簡單：&lt;strong&gt;整個城市都有我的好幫手&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那時候國外的群眾外包（crowdsourcing）模式正紅，美國有 TaskRabbit、澳洲有 Airtasker——你有一件懶得做、或做不來的事：排隊買票、活動攝影、跑腿代買、家裡臨時需要人手，就把任務丟上平台，附近有空、想賺點外快的人接走。城市裡每個人的零碎時間，變成另一個人的解方。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;澳洲 Airtasker，我們當時最主要的參考對象。同樣的概念，我們想做台灣版。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;我們覺得這在台灣一定行。年輕、沒包袱、帳戶裡還有一點錢，再加上一股「這東西我做得出來」的傻勁，就開始了。&lt;/p&gt;
&lt;p&gt;我那時候是全端工程師，前端、後端、雙平台 App，一個人扛。技術選了 PHP、Codeigniter(當時還沒有 Laravel)，後面接 AWS。團隊很小，所以沒有大公司那套流程，就是想到一個功能、刻出來、丟上線、看使用者怎麼反應，再改。JEJ 負責商務開發（BD），技術全是我扛，這樣就開幹了。&lt;/p&gt;
&lt;p&gt;那時候還沒有很多人懂「群眾外包」這個詞，所以我們先從 Facebook 開始養人氣。粉絲頁的 slogan 是「台灣人出任務！」——用任務感替代「幫我找人」的尷尬感。正式開站前，粉絲頁就已經累積到接近一萬五千位追蹤者。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Citytasker Facebook 粉絲頁，一萬五千位追蹤者——開站前靠這個建立了一點信心，至少讓我們相信有人等著用。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;我們還做了一支蝙蝠俠影片&quot;&gt;我們還做了一支蝙蝠俠影片&lt;/h2&gt;
&lt;p&gt;要讓人懂「找陌生人幫你做事」這件事，光用講的很無聊。所以我們拍了一支介紹影片。&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/bz6AGtN3iFE&quot; title=&quot;CityTasker 蝙蝠俠介紹影片&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;設定是這樣的：有一座叫「高潭市」的城市，永遠充滿混亂，高端局長每天頭痛得要命，好在他只要呼叫幫手，問題很快就解決了。然後鏡頭一轉——「但回歸現實，你住的不是高潭市，你也不是高端局長。當你有解決不了的問題時，該去哪裡找你的超級英雄呢？」&lt;/p&gt;
&lt;p&gt;對，就是蝙蝠俠那個高譚市。一群沒什麼預算的年輕人，硬是用這個梗，把產品講成一個「召喚城市英雄」的故事。現在回頭看有點中二，但我到今天還是覺得那個點子很可愛。&lt;/p&gt;
&lt;h2 id=&quot;入選-appworks-第七屆&quot;&gt;入選 AppWorks 第七屆&lt;/h2&gt;
&lt;p&gt;真正讓我們覺得「這好像是玩真的」的，是入選了 &lt;strong&gt;AppWorks 第七屆（#7）&lt;/strong&gt; 創業育成計劃。&lt;/p&gt;
&lt;p&gt;對當時的我們來說，這是一張門票。突然之間，身邊都是一群跟你一樣、眼睛發亮、想用一個 app 改變世界的人。我還記得跑去跟 Jamie（林之晨）要簽名，空氣裡瀰漫著一種「hack everything」、什麼都能被重新發明的氣氛。第七屆的 Demo Day 結束後，連《數位時代》都報導了我們。&lt;/p&gt;
&lt;p&gt;那種「我們是被選中的」感覺，很迷人。迷人到讓你很容易忘記一件事：&lt;strong&gt;入選育成計劃，跟做出一門生意，是兩回事。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2014 年初，我們三個人在 AppWorks 辦公室留下的合照。那時候充滿熱血。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;那天之後呢&quot;&gt;那天之後呢？&lt;/h2&gt;
&lt;p&gt;開站那天的 837 個註冊，是我創業生涯裡少數幾個純粹的快樂時刻。&lt;/p&gt;
&lt;p&gt;說 837 很少？我知道看起來像這樣。但你要把背景放進去——&lt;/p&gt;
&lt;p&gt;我們沒有買廣告，沒有請網紅業配，沒有媒體稿，連 App Store 排名都還不存在。那天的流量，幾乎全部來自一件事：粉絲頁的一則貼文、幾個朋友的分享，然後就靠口碑滾動。從 0 到 837，發生在不到一個工作天。&lt;/p&gt;
&lt;p&gt;更讓我興奮的，不是數字本身——是這 837 個人裡，有人真的開始用了。&lt;/p&gt;
&lt;p&gt;開站後幾個小時，後台就出現了第一批任務：「幫我排長榮訂位」、「台北有沒有人能幫我顧貓一個週末」、「攝影師，活動需要兩小時，可以議價」。每一筆任務通知信寄出去，我幾乎都會點進來看一遍。54 封簡訊發出去，代表有 54 個接任務的人被叫起來，這不是假資料，是真實的媒合行為發生了。&lt;/p&gt;
&lt;p&gt;我記得傍晚 JEJ 傳了一張截圖給我——是某個任務底下，有人留言詢問。他後面加了一個句點：「有人用。」就這樣。兩個字。&lt;/p&gt;
&lt;p&gt;就這樣，我們互看一眼，笑了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;那天最高 50 人同時在線，對一個什麼都沒有的小平台來說，那個數字是一個訊號——&lt;strong&gt;平台有流量，並不等於平台有價值&lt;/strong&gt;，這是後來慢慢才懂的事。但那個傍晚，我還不懂，也不想懂。&lt;/p&gt;
&lt;p&gt;把平台從「有人來看」一路畫到「能活下去」，就能看出我們當天其實只走到中段：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/citytasker-launch-day-story/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我只想跟這 837 個人說一聲謝謝：謝謝你們那天不嫌棄，真的進來了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;但你大概已經猜到了——這個故事的結局，不是我們成功了。&lt;/p&gt;
&lt;p&gt;CityTasker 最後沒能熬過去。資金燒完、市場定位卡住、路上還撞到幾道我完全沒想過的牆——包括被同行檢舉、收到台北市政府勞動局的罰單。那是接下來幾篇要慢慢講的事。&lt;/p&gt;
&lt;p&gt;我想先把這個開場留在這裡：留在那個還相信一切都會成的傍晚，留在那個一邊按 F5、一邊跟夥伴傻笑的我。&lt;/p&gt;
&lt;p&gt;因為接下來要復盤的每一件事——燒錢的速度、被同行盯上的那通電話、那張來自勞動局的罰單、那句「我們不是那 1%」——都是從這個傍晚出發的。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.6qJ646PR.webp" medium="image"/><category>創業</category><category>AppWorks</category><category>CityTasker</category><category>新創</category><category>職涯反思</category><category>群眾外包</category><enclosure url="https://bobochen.dev/_astro/cover.6qJ646PR.webp" length="0" type="image/png"/></item><item><title>不是那 1% 的人：十年後，我怎麼看 CityTasker 這場沒成功的創業</title><link>https://bobochen.dev/blog/citytasker-not-the-one-percent/</link><guid isPermaLink="true">https://bobochen.dev/blog/citytasker-not-the-one-percent/</guid><description>創業圈總在歌頌那 1% 成功的人，剩下 99% 呢？十年後回看 CityTasker——一場燒完錢、親手收掉的創業，我想重新定義什麼叫「失敗」，以及這段沒成功的經歷，後來怎麼變成我做到研發部副處長、技術總監的底氣。</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;CityTasker 是 2012 到 2014 年，我和朋友做的一個任務媒合平台——「整個城市都有我的好幫手」。入選了 AppWorks 第七屆育成計畫，開啟了不一樣的歷程，雖然公司後來被併購，故事最後結局還是燒完錢收攤了。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2014 年初，AppWorks 的辦公室。我們團隊三人（中間是當年青澀的我）&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;收掉公司那天我覺得自己是輸家&quot;&gt;收掉公司那天，我覺得自己是輸家&lt;/h2&gt;
&lt;p&gt;口袋見底的感覺，是一點一點來的。&lt;/p&gt;
&lt;p&gt;不是某個戲劇性的早晨醒來發現帳戶歸零，而是你眼睜睜看著數字往下掉，知道再過幾個月就撐不住了，卻找不到任何能讓它停下來的方法。最後我們做了那個誰都不想做的決定：先散了吧，各自回去上班。隊友轉去了旅遊產業，我把履歷打開，重新變回一個「找工作的工程師」。&lt;/p&gt;
&lt;p&gt;那段時間我很難跟人說清楚自己在幹嘛。前一年還在 Demo Day 上意氣風發，被&lt;a href=&quot;https://www.bnext.com.tw/article/30113/BN-ARTICLE-30113&quot;&gt;《數位時代》報導&lt;/a&gt;，轉眼就要去面試別人的公司、當一個打工仔。我心裡有個聲音很清楚：你失敗了。你不是那種會成的人。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;前一年的高光時刻：第七屆 AppWorks Demo Day，台上是 CityTasker，台下坐滿了人。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;那-1-的迷思&quot;&gt;那 1% 的迷思&lt;/h2&gt;
&lt;p&gt;創業圈是一個很會說故事的地方。&lt;/p&gt;
&lt;p&gt;我們聽到的，永遠是那些成的人——某某 App 被收購、某某團隊估值幾億、某某創辦人三十歲財富自由。這些故事很迷人，迷人到你會以為那是常態。但真相是，那只是 1%。剩下的 99%，安安靜靜地燒完錢、關掉網站、回去上班，沒有人替他們寫報導。&lt;/p&gt;
&lt;p&gt;而我，就是那 99%。&lt;/p&gt;
&lt;p&gt;我花了好幾年才願意承認這件事，又花了更久才想通：問題不在於我是 99%，而在於我一直用 1% 的標準，去審判一段根本不該那樣被衡量的經歷。&lt;/p&gt;
&lt;h2 id=&quot;重新定義失敗&quot;&gt;重新定義「失敗」&lt;/h2&gt;
&lt;p&gt;有句話我放在心裡很久——大意是：沒被開除過、沒開除過人、沒燒完錢結束過一間公司、沒領過失業救濟金，其實都不算什麼重大挫敗。&lt;/p&gt;
&lt;p&gt;當年我把它讀成自嘲。十年後再讀，我把它反過來想。&lt;/p&gt;
&lt;p&gt;我燒完了自己的錢。我親手把一間公司收掉。我一個人扛過前端、後端、雙平台 App，撞過從沒想過的法規牆，還活了下來。這些事，聽起來像一串失敗清單，但你仔細看——這是多數人一輩子都不會有機會經歷的事。大部分人從進職場到退休，從來沒有真正「擁有」過一個東西，沒有為它的生死負過全責。&lt;/p&gt;
&lt;p&gt;我有過。它沒成，但它確確實實是我的。失敗，原來不是經歷的相反，它本身就是一種少數人才有的經歷。&lt;/p&gt;
&lt;h2 id=&quot;創業沒給我財富給了我視角&quot;&gt;創業沒給我財富，給了我視角&lt;/h2&gt;
&lt;p&gt;那兩年最值錢的，不是任何一行我寫過的程式。&lt;/p&gt;
&lt;p&gt;是它逼著一個只會寫 code 的人，去做完整個 0 到 1：自己分析需求、自己選技術、自己帶人、自己跟外面的世界溝通。我第一次知道一個產品從無到有要繞過多少看不見的坑，第一次知道「做得出來」和「有人要用」中間隔著一整個世界。&lt;/p&gt;
&lt;p&gt;這些東西，後來全都回來找我。從 17 Media 的資深後端工程師、Snapask 的後端技術總監，到現在 QT Medical 的系統處副處長——每一個位子要我做的判斷，幾乎都能在 CityTasker 那兩年找到原型。我能比較沉得住氣地看一個產品、看一個團隊、看一個還沒被驗證的方向，是因為我年輕時用自己的錢，把這堂課完整上過一遍。&lt;/p&gt;
&lt;p&gt;這筆「學費」沒有在公司收掉時歸零，而是用十年的時間換了一種形式回來：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/citytasker-not-the-one-percent/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;創業沒讓我變有錢。它給了我一副別人花錢也買不到的眼睛。&lt;/p&gt;
&lt;h2 id=&quot;給那個按-f5-的傍晚&quot;&gt;給那個按 F5 的傍晚&lt;/h2&gt;
&lt;p&gt;所以如果能回到 2013 年那個傍晚——那個盯著後台數字一格一格往上跳、跟夥伴傻笑著以為要成功了的我——我不會劇透結局，也不會叫他別跳。&lt;/p&gt;
&lt;p&gt;我只想拍拍他的肩膀，告訴他：你不會成為那 1%，這件事到頭來沒那麼重要。你以為這是一場你會贏或會輸的賭局，但其實，你正在領的，是一筆要十年後才會到帳、而且永遠不會貶值的東西。&lt;/p&gt;
&lt;p&gt;好好享受這個傍晚吧。後面的路，比你想的長，也比你想的好。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.DpyjKR2p.webp" medium="image"/><category>創業</category><category>AppWorks</category><category>CityTasker</category><category>新創</category><category>職涯反思</category><category>失敗復盤</category><enclosure url="https://bobochen.dev/_astro/cover.DpyjKR2p.webp" length="0" type="image/png"/></item><item><title>速度比完美更重要：創業教我的事，後來都變成我帶團隊的原則</title><link>https://bobochen.dev/blog/citytasker-speed-over-perfection/</link><guid isPermaLink="true">https://bobochen.dev/blog/citytasker-speed-over-perfection/</guid><description>CityTasker 是我 2012 年和朋友做的任務媒合平台。當年沒資源、沒時間，「速度比完美更重要」是被現實逼出來的生存本能；十年後當我帶團隊一路做到研發部副處長、技術總監，這套心態反而成了我刻意選擇的管理原則。</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2 id=&quot;想到一個功能當晚就上線&quot;&gt;想到一個功能，當晚就上線&lt;/h2&gt;
&lt;p&gt;2012 到 2014 年，我和朋友做了一個叫 CityTasker 的東西——一個任務媒合平台，slogan 是「整個城市都有我的好幫手」。你有件懶得做的事，丟上來，附近有空的人接走。&lt;/p&gt;
&lt;p&gt;那時候我是 Co-founder 兼全端工程師，前端、後端、雙平台 App，一個人扛。團隊小到不行，所以我們沒有什麼「規格先評審、設計先過稿」的流程。一個功能想到了，覺得使用者可能會要，當晚就刻，刻完隔天就丟上線，然後盯著後台看有沒有人用。&lt;/p&gt;
&lt;p&gt;沒人用，就拿掉。有人用，就接著改。&lt;/p&gt;
&lt;p&gt;現在回頭看，那根本不是什麼方法論，那是窮人的本能。&lt;/p&gt;
&lt;h2 id=&quot;完美是有錢人的奢侈品&quot;&gt;完美是有錢人的奢侈品&lt;/h2&gt;
&lt;p&gt;我得老實說：當年選擇「先上線再說」，不是因為我們讀過什麼敏捷開發的書，而是因為我們別無選擇。&lt;/p&gt;
&lt;p&gt;帳戶裡的錢每天都在變少。你沒有三個月去把一個功能磨到完美，因為三個月後公司可能就不在了。完美是一種需要時間和金錢餵養的東西，而那兩樣我們都沒有。我們唯一有的，是「先讓它活著、再看市場給不給臉」的急迫感。&lt;/p&gt;
&lt;p&gt;所以我們學會了一件事：&lt;strong&gt;先把不完美的東西丟出去，讓真實的使用者告訴你哪裡錯，遠比關起門來自己猜要快得多&lt;/strong&gt;。一個你覺得很完美、但沒人要的功能，跟一個醜陋、卻有人天天用的功能比起來，後者的價值高出太多。&lt;/p&gt;
&lt;p&gt;那時候我以為這只是創業的求生技巧。我沒想到，它後來會變成我帶團隊的底層信念。&lt;/p&gt;
&lt;h2 id=&quot;從求生本能變成刻意的選擇&quot;&gt;從求生本能，變成刻意的選擇&lt;/h2&gt;
&lt;p&gt;CityTasker 燒完錢收攤後，我回去上班，接著一路在軟體開發、技術管理、DevOps、雲端架構裡打滾，從 17 Media 的資深後端工程師、Snapask 的後端技術總監，做到現在 QT Medical 的系統處副處長。&lt;/p&gt;
&lt;p&gt;帶的人愈來愈多，我發現一件有趣的事：當年那個被現實逼出來的「速度比完美重要」，現在變成了我&lt;strong&gt;刻意&lt;/strong&gt;選擇的管理原則。差別在於——以前是因為沒得選，現在是因為我知道它真的有用。&lt;/p&gt;
&lt;p&gt;具體來說，它長成幾個樣子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;敏捷，但不是因為時髦&lt;/strong&gt;。小步快跑、快速迭代，是因為我親身體會過「猜錯方向但跑得快」可以及時掉頭，「猜對方向卻跑得慢」反而被市場拋下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公開透明的溝通&lt;/strong&gt;。創業時資訊就在我們幾個人腦袋裡，藏不住也不必藏。帶團隊後我刻意維持這件事：不藏決策的理由、不藏壞消息。資訊一旦變成少數人的特權，速度第一個死。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;鼓勵主動承擔、勇於實驗&lt;/strong&gt;。我希望團隊裡的人敢自己做決定、敢試錯，而不是每件事都等我點頭。能自己接住任務、自己往前推的人，才跑得起來。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;把失敗當養分，而不是拿來究責&lt;/strong&gt;。這大概是 CityTasker 給我最深的一課。一個沒成功的創業教會我，面對失敗可以更坦然，把它當成長的養分，而非絕對的挫敗。所以團隊裡有人實驗失敗了，我第一個問的不是「誰的錯」，而是「我們從中學到什麼」。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;但速度比完美也有它的代價&quot;&gt;但「速度比完美」也有它的代價&lt;/h2&gt;
&lt;p&gt;不過十年下來，我也學到這句話不能無限上綱。&lt;/p&gt;
&lt;p&gt;「先上線再說」的另一面，是技術債、是品質的妥協、是你某天得回頭還的帳。創業時我可以說「反正公司能不能活到下個月都不知道，債以後再說」——但帶一個要長期經營的產品和團隊時，這句話會害死你。&lt;/p&gt;
&lt;p&gt;所以現在的我，拿捏的方式變了。我會問：這次的不完美，是「可以之後再補」的那種，還是「補不回來」的那種？使用者體驗的小瑕疵、之後可以重構的程式碼，先上；但牽涉到資料安全、病人安全、信任這種補不回來的東西，我寧可慢。&lt;/p&gt;
&lt;p&gt;速度依然重要，但成熟一點的版本是：&lt;strong&gt;該快的地方快到底，該慢的地方有膽量慢下來&lt;/strong&gt;。而分辨這兩者，正是十年來我一直在練的功課。&lt;/p&gt;
&lt;p&gt;我現在用的判斷方式，其實就是先問「這個錯誤之後補得回來嗎？」：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/citytasker-speed-over-perfection/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;citytasker-真正留給我的&quot;&gt;CityTasker 真正留給我的&lt;/h2&gt;
&lt;p&gt;那場創業沒有成功。錢燒完了，口袋見底，我們先回去上班，隊友轉去了旅遊產業。從世俗的標準看，它是個失敗的故事。&lt;/p&gt;
&lt;p&gt;但「速度比完美更重要」這句話，跟著我走了十年，從一個窮學生的求生本能，長成一個技術主管的管理哲學。它幫我帶過好幾個團隊、撐過好幾個產品。&lt;/p&gt;
&lt;p&gt;如果有人問我 CityTasker 留下了什麼——不是那些註冊數字，也不是 AppWorks 的光環。是這套被現實狠狠教過、之後我又選擇相信一輩子的做事方式。&lt;/p&gt;
&lt;p&gt;這大概就是一場沒成功的創業，最值錢的遺產。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.Dohn-98-.webp" medium="image"/><category>創業</category><category>AppWorks</category><category>CityTasker</category><category>職涯反思</category><category>技術管理</category><category>團隊管理</category><enclosure url="https://bobochen.dev/_astro/cover.Dohn-98-.webp" length="0" type="image/png"/></item><item><title>賣掉之後呢：CityTasker 最意外的結局，是連買下我們的公司也不在了</title><link>https://bobochen.dev/blog/citytasker-sudo-acquisition/</link><guid isPermaLink="true">https://bobochen.dev/blog/citytasker-sudo-acquisition/</guid><description>我說 CityTasker 燒完錢收攤了——那是真的，但不是全部。它後來其實被一間 AppWorks 認識的獵頭顧問公司 sudo 買下，從合作、一頓頓早餐會、到一紙 2016 年的收購合約。而最意外的結局是：連買下我們的那間公司，後來也不在了。</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我在前面說過，CityTasker 燒完錢、收攤了。&lt;/p&gt;
&lt;p&gt;那是真的。但不是全部。&lt;/p&gt;
&lt;p&gt;有一段我一直沒講——因為它太安靜了，安靜到連我自己都差點以為它不算數。CityTasker 並沒有真的死在 2014 年那個收攤的決定裡。它後來，被賣掉了。&lt;/p&gt;
&lt;h2 id=&quot;收攤不等於結束&quot;&gt;收攤，不等於結束&lt;/h2&gt;
&lt;p&gt;公司收掉之後，團隊是散了，但 CityTasker 這個東西、還有我們在創業圈裡結下的關係，沒有跟著散。&lt;/p&gt;
&lt;p&gt;故事要從 AppWorks 說起。我們那屆育成裡，還有另一支團隊，叫 sudo——一間做獵頭、人力顧問的公司。創業圈很小，同一個屋簷下一起被孵化，低頭不見抬頭見，我們很自然就熟了。&lt;/p&gt;
&lt;p&gt;一開始只是合作。他們媒合的是「人才」，我們媒合的是「任務」，中間有不少搭得上的地方，就你幫我、我幫你地來往著。&lt;/p&gt;
&lt;h2 id=&quot;那些早餐會&quot;&gt;那些早餐會&lt;/h2&gt;
&lt;p&gt;真正讓事情慢慢長出來的，是早餐。&lt;/p&gt;
&lt;p&gt;sudo 的創辦人會定期約我們吃早餐。沒有什麼正式議程，就是邊吃邊聊——聊產品、聊團隊、聊各自卡在哪裡。現在回頭看，那一頓一頓的早餐，其實是一段很慢、很有耐心的「互相了解」。&lt;/p&gt;
&lt;p&gt;創業的時候，我一直以為談事情要靠 pitch deck、靠估值表、靠會議室裡正襟危坐的那一套。後來才知道，很多真正重要的事，是在一張早餐桌上、配著一杯溫豆漿談成的。&lt;/p&gt;
&lt;h2 id=&quot;一紙合約&quot;&gt;一紙合約&lt;/h2&gt;
&lt;p&gt;2016 年，我們簽了一份合約。&lt;/p&gt;
&lt;p&gt;CityTasker，賣給了 sudo。&lt;/p&gt;
&lt;p&gt;那一刻的感覺，我到現在都說不太清楚。它不像 2014 年收攤那樣，有明確的失落；也不像被大公司高價收購那樣風光。它就是——一個我親手做出來、又親手收掉的東西，終於找到了一個願意接手的人。&lt;/p&gt;
&lt;p&gt;這算成功出場嗎？還是只是換一種方式收攤？我到今天都還沒有標準答案。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2016 年的那紙收購契約書。一個親手做出來的東西，最後用一份合約交了出去。&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;然後買下我們的人也不在了&quot;&gt;然後，買下我們的人也不在了&lt;/h2&gt;
&lt;p&gt;但這個故事真正讓我意外的，不是「賣掉」這件事本身。&lt;/p&gt;
&lt;p&gt;是沒過多久，sudo 自己，也收了。&lt;/p&gt;
&lt;p&gt;那個曾經坐在我對面、一口一口早餐跟我談下 CityTasker 的人，他的公司，後來也走進了跟我們一樣的結局。&lt;/p&gt;
&lt;p&gt;我一直以為，我是把 CityTasker 交到了一個「比我們更穩、更會做生意」的人手上——一個更像那 1% 的人。但事實證明，沒有誰是穩的。在這個圈子裡，今天看起來活得好好的人，明天可能就不在了。我是這樣，他也是。&lt;/p&gt;
&lt;h2 id=&quot;真正活得比公司久的東西&quot;&gt;真正活得比公司久的東西&lt;/h2&gt;
&lt;p&gt;所以 CityTasker 到底死了幾次？&lt;/p&gt;
&lt;p&gt;把公司與關係分成兩條時間線，就會發現真正留下來的不是法人或產品名稱：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/citytasker-sudo-acquisition/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;收攤算一次，賣掉算一次，連買下它的人都消失，再算一次。照理說，這應該是一個徹底失敗、徹底歸零的故事。&lt;/p&gt;
&lt;p&gt;但奇怪的是，留下來的東西，比我以為的多。&lt;/p&gt;
&lt;p&gt;不是公司——公司早就沒了。是那張在 AppWorks 結下的關係網，是那些早餐桌上的對話，是「原來一件事可以這樣慢慢談成」的那種篤定。這些東西，比任何一間公司都活得久。&lt;/p&gt;
&lt;p&gt;我花了整整一個系列，想搞懂自己到底算不算那 1%。寫到這裡，我終於確定：這個問題，從一開始就問錯了。&lt;/p&gt;
&lt;p&gt;公司會收、會賣、會被接手的人再收掉一次。但你在過程裡認識的人、你學會看世界的方式，不會。那才是真正不會貶值的東西。&lt;/p&gt;
&lt;p&gt;CityTasker 死了三次。但它，從來沒有真的離開我。&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.dXyCgqnf.webp" medium="image"/><category>創業</category><category>AppWorks</category><category>CityTasker</category><category>併購</category><category>新創</category><category>職涯反思</category><enclosure url="https://bobochen.dev/_astro/cover.dXyCgqnf.webp" length="0" type="image/png"/></item><item><title>Agentic Engineering 的下一步：2026 之後，工程師還需要寫 code 嗎？</title><link>https://bobochen.dev/blog/agentic-engineering-future-and-you/</link><guid isPermaLink="true">https://bobochen.dev/blog/agentic-engineering-future-and-you/</guid><description>Benchmark 進步很快，但不能直接外推明年的能力。從一年實戰整理工程師仍需負責的判斷、協調、品味與風險，並附一份可自行調整的能力維持練習。</description><pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;這是「Agentic Engineering 實戰手冊」系列第十四篇。上一篇：&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-team-adoption&quot;&gt;團隊導入&lt;/a&gt;。後面還有兩篇實戰補充。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;benchmark-進步很快但不能畫直線預測明年&quot;&gt;Benchmark 進步很快，但不能畫直線預測明年&lt;/h2&gt;
&lt;p&gt;SWE-bench Verified 的成績曾在一年內快速上升，這件事是真的；問題是 leaderboard 會隨模型、harness、工具與提交規則改變，不能把兩個時間點連成直線，再預測 2027 年會到 95%。&lt;/p&gt;
&lt;p&gt;另一個常被引用的比較，是 &lt;a href=&quot;https://scale.com/blog/swe-bench-pro&quot;&gt;Scale 在 2025 年 9 月推出 SWE-bench Pro&lt;/a&gt;時，頂尖系統在 Verified 已超過 70%，在 Pro 約 23%。這是不同資料集與評測環境的歷史快照，不是今天的即時成績，也不能直接相減成「真實能力差 49%」。&lt;/p&gt;
&lt;p&gt;Benchmark 能告訴我們模型在特定題目、工具與規則下的表現，不能單獨回答你的 repo 是否安全可用。&lt;a href=&quot;https://www.gartner.com/en/newsroom/press-releases/2025-06-25-gartner-predicts-over-40-percent-of-agentic-ai-projects-will-be-canceled-by-end-of-2027&quot;&gt;Gartner 對 2027 年底超過 40% agentic AI 專案可能取消的預測&lt;/a&gt;，談的也是成本、價值與風控，不是 benchmark 正確率。&lt;/p&gt;
&lt;p&gt;我不打算做預測，那是 pundit 的工作。作為一個 practitioner，我更感興趣的是：不管 agent 的能力怎麼變，什麼是不變的？&lt;/p&gt;
&lt;h2 id=&quot;2026-年哪些事仍要有人負責&quot;&gt;2026 年，哪些事仍要有人負責&lt;/h2&gt;
&lt;p&gt;經過一年的實戰，我整理出五類不該因 agent 變強就放棄人類 ownership 的工作。Agent 可以參與，甚至做得很好；但責任不能跟著 prompt 一起丟出去。&lt;/p&gt;
&lt;h3 id=&quot;1-釐清模糊的需求&quot;&gt;1. 釐清模糊的需求&lt;/h3&gt;
&lt;p&gt;「用戶反映登入流程太複雜。」&lt;/p&gt;
&lt;p&gt;這句話背後可能意味著 20 種不同的改善方向。人類工程師會去找 PM 問、看 user research、在白板上畫 flow——然後形成一個具體的方案。&lt;/p&gt;
&lt;p&gt;Agent 可能追問、整理假設或提出多個方向，也可能直接選一個解釋開始寫。關鍵是有人要確認：它解決的是使用者問題，還是只把句子翻成 code。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/spec-driven-development-for-agents&quot;&gt;Spec-Driven Development&lt;/a&gt; 可以緩解這個問題。Spec 可由 agent 起草，但了解使用者與業務的人要確認假設、範圍與驗收。&lt;/p&gt;
&lt;h3 id=&quot;2-組織政治與跨團隊協調&quot;&gt;2. 組織政治與跨團隊協調&lt;/h3&gt;
&lt;p&gt;「這個 API 改動需要後端團隊同意」、「那個 PM 特別在意 performance」、「QA 組長不喜歡我們用太多 mock」。&lt;/p&gt;
&lt;p&gt;有些 organizational context 能被文件化，也能讓 agent 協助整理；但利害關係、承諾與衝突需要當事人溝通，不能只靠模型替團隊協調。&lt;/p&gt;
&lt;h3 id=&quot;3-品味與審美判斷&quot;&gt;3. 品味與審美判斷&lt;/h3&gt;
&lt;p&gt;「這個 UX 好嗎？」不是一個有標準答案的問題。&lt;/p&gt;
&lt;p&gt;Agent 可以完美實現你 spec 裡描述的每一個細節。但如果你的 spec 沒有描述「這個按鈕在這裡感覺太擠」，agent 不會自己發現。&lt;/p&gt;
&lt;p&gt;Agent 可以產生設計選項、做 heuristic review，甚至從使用數據找問題；產品 owner 仍要選擇哪種體驗符合品牌、使用者與當下限制，並為結果負責。&lt;/p&gt;
&lt;h3 id=&quot;4-倫理和合規判斷&quot;&gt;4. 倫理和合規判斷&lt;/h3&gt;
&lt;p&gt;「這個功能收集了 PII，需要通知用戶嗎？」
「我們的 AI feature 在歐盟受 AI Act 規範嗎？」
「這個 dark pattern 在法律上可以但在道德上該不該用？」&lt;/p&gt;
&lt;p&gt;Agent 可以幫你找來源、整理 checklist，但法律、合規與倫理的高風險判斷要由具備職責與專業的人確認。模型不能承擔 accountability。&lt;/p&gt;
&lt;h3 id=&quot;5-真正創新的架構設計&quot;&gt;5. 真正創新的架構設計&lt;/h3&gt;
&lt;p&gt;Agent 非常擅長 follow 既有的 patterns。你的 codebase 用了 MVC，它就會寫 MVC。你用了 event-driven，它就寫 event-driven。&lt;/p&gt;
&lt;p&gt;Agent 也能提出新穎組合或反駁既有 pattern；難的是判斷新方案是否真能在你的限制下成立。創意可以共同產生，架構決策與後果仍要有人承擔。&lt;/p&gt;
&lt;h2 id=&quot;仍需由人承擔的能力與責任&quot;&gt;仍需由人承擔的能力與責任&lt;/h2&gt;
&lt;p&gt;把以上歸納為四種核心能力：&lt;/p&gt;
&lt;h3 id=&quot;判斷力&quot;&gt;判斷力&lt;/h3&gt;
&lt;p&gt;知道什麼該做、什麼不該做。&lt;/p&gt;
&lt;p&gt;Agent 可以執行很多事，也可以提供取捨建議；「這件事值不值得做」、「做到什麼程度就夠了」、「誰承擔風險」仍需要負責人決定。&lt;/p&gt;
&lt;p&gt;在 agentic workflow 裡，你做的每一個決策的價值都被放大了。因為 agent 會忠實地執行你的決策：好的決策會被高效執行，壞的決策也會。&lt;/p&gt;
&lt;h3 id=&quot;品味&quot;&gt;品味&lt;/h3&gt;
&lt;p&gt;不是「能不能做」，而是「該不該這樣做」。&lt;/p&gt;
&lt;p&gt;好的 API 設計、UX 與 code 結構都需要 taste。Agent 可以在 constraints 內提出方案，但沒有一個客觀的「最優解」保證；constraints 怎麼定、哪個方案符合產品語境，仍需要品味與使用者回饋。&lt;/p&gt;
&lt;h3 id=&quot;組織-context&quot;&gt;組織 Context&lt;/h3&gt;
&lt;p&gt;理解公司政治、團隊動態、stakeholder 的優先級。&lt;/p&gt;
&lt;p&gt;除非你主動提供，agent 不會知道 CTO 最近在推 microservices、PM 下個月要 demo 給投資人看，或 QA 上週才因為測試不足而處理 production bug。這些組織 context 會直接影響優先級、溝通與取捨。&lt;/p&gt;
&lt;h3 id=&quot;倫理決策&quot;&gt;倫理決策&lt;/h3&gt;
&lt;p&gt;合規、隱私、安全的最終判斷。&lt;/p&gt;
&lt;p&gt;這不只關乎能力，而是 accountability。涉及 ethics 的決策可以有 agent 輔助研究，但責任要落在明確的人與組織流程上。&lt;/p&gt;
&lt;h2 id=&quot;技能衰退還是技能轉型&quot;&gt;技能衰退？還是技能轉型？&lt;/h2&gt;
&lt;p&gt;回到 &lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-mindset-shift&quot;&gt;Post 2&lt;/a&gt; 討論過的身份認同議題：用了一年 agent，我的技能有什麼變化？&lt;/p&gt;
&lt;h3 id=&quot;衰退的能力&quot;&gt;衰退的能力&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;語法記憶&lt;/strong&gt;：我已經忘記很多 API 的具體參數了。以前能憑記憶寫出完整的 &lt;code&gt;Array.reduce&lt;/code&gt;，現在常常要想一下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;手寫 boilerplate 的速度&lt;/strong&gt;：以前 30 分鐘能寫完一個 CRUD endpoint，現在手寫可能要 45 分鐘（因為不常練了）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;某些 debug 直覺&lt;/strong&gt;：以前看到 error message 就大概知道是什麼問題。現在我傾向先丟給 agent，而不是自己分析。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;增長的能力&quot;&gt;增長的能力&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;系統設計能力&lt;/strong&gt;：因為 agent 處理了 implementation 細節，我花更多時間想架構、想 trade-off。設計能力反而變強了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需求分析能力&lt;/strong&gt;：寫 &lt;a href=&quot;https://bobochen.dev/blog/spec-driven-development-for-agents&quot;&gt;spec&lt;/a&gt; 寫多了，分析和拆解需求的能力提升了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review 眼光&lt;/strong&gt;：看了一年 agent 的 code，我對 &lt;a href=&quot;https://bobochen.dev/blog/agent-output-verification-review&quot;&gt;hallucination pattern&lt;/a&gt; 的敏感度提高了很多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;溝通表達能力&lt;/strong&gt;：寫好的 spec 就是清楚的溝通。這個技能也提升了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;淨結果&quot;&gt;淨結果&lt;/h3&gt;
&lt;p&gt;對我而言，目前是划算的交換：部分手寫速度下降，需求拆解與 review 經驗增加。這兩者都只是個人觀察；使用 agent 不會自動把省下的時間變成更好的判斷力。&lt;/p&gt;
&lt;p&gt;但我要誠實地說，這讓我偶爾感到不安。有時候同事問我一個 syntax 問題，我需要查一下才能回答，心裡會覺得「以前我是可以秒答的」。&lt;/p&gt;
&lt;p&gt;這種不安很正常。退一步看，查 syntax 並不可恥；同樣地，維持足夠的實作與除錯能力也很重要，否則你連 agent 的錯都看不出來。兩邊不必互相貶低。&lt;/p&gt;
&lt;h2 id=&quot;一份防衰退訓練計畫&quot;&gt;一份「防衰退」訓練計畫&lt;/h2&gt;
&lt;p&gt;不管你對未來怎麼看，以下的練習都不會浪費。它們同時鍛練「跟 agent 協作」和「agent 做不到的事」。&lt;/p&gt;
&lt;h3 id=&quot;8-週循環計畫&quot;&gt;8 週循環計畫&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Week 1-2：Context Engineering 練習&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目標：讓你的 &lt;a href=&quot;https://bobochen.dev/blog/claude-md-rules-files-masterclass&quot;&gt;CLAUDE.md&lt;/a&gt; 的品質提升一個等級&lt;/li&gt;
&lt;li&gt;練習：review 你的 CLAUDE.md，問自己「agent 重複犯的錯哪些可以靠加一條 rule 解決？」&lt;/li&gt;
&lt;li&gt;副作用：練習精確溝通的能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Week 3-4：Spec Writing 練習&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目標：挑中型以上 task，先寫 &lt;a href=&quot;https://bobochen.dev/blog/spec-driven-development-for-agents&quot;&gt;spec&lt;/a&gt; 再交給 agent；小 typo 不必儀式化&lt;/li&gt;
&lt;li&gt;練習：用 Goal / Constraints / Verification 格式寫 spec&lt;/li&gt;
&lt;li&gt;副作用：練習需求分析和拆解的能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Week 5-6：Review 練習&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目標：刻意練習找 agent code 裡的問題&lt;/li&gt;
&lt;li&gt;練習：review agent 的 PR 時，記錄你找到的每一個問題，分類是「邏輯錯誤」還是「事實錯誤」&lt;/li&gt;
&lt;li&gt;副作用：提升 &lt;a href=&quot;https://bobochen.dev/blog/agent-output-verification-review&quot;&gt;code review&lt;/a&gt; 的效率和敏感度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Week 7-8：理解與除錯練習&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;目標：保持基礎能力的手感&lt;/li&gt;
&lt;li&gt;練習：每兩週選一個小功能，先自己拆解與除錯；是否完全不用 agent，可依你的學習目標決定&lt;/li&gt;
&lt;li&gt;副作用：當 agent 出問題時你還有能力自己 debug&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這四段是一輪循環，不是做完就結束的清單；每一段真正留下來的，是「副作用」那一行。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-future-and-you/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;持續練習&quot;&gt;持續練習&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;每月一次架構設計&lt;/strong&gt;：先獨立寫出方案與 trade-off，再請 agent 挑戰假設；避免第一步就被它的框架錨定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定期做能力抽查&lt;/strong&gt;：挑一段你必須能自行維護的 code，確認自己仍能解釋、測試與 debug；不必為了儀式感硬排 agent-free day。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;信任不是一個百分比&quot;&gt;信任不是一個百分比&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-what-is-it&quot;&gt;第一篇&lt;/a&gt;已經修正了一個常見誤讀：90% 的受訪者使用 AI，不等於 90% 在用 coding agent；我也找不到足以支持「只有 29% 信任」的同口徑官方數據。&lt;/p&gt;
&lt;p&gt;真正有用的問題不是「你信 agent 幾分」，而是「你願意在什麼條件下，把哪一類任務交給它」。例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它會 hallucinate（→ 你建了 &lt;a href=&quot;https://bobochen.dev/blog/agent-output-verification-review&quot;&gt;品質保證流程&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;它不懂你的專案（→ 你設計了 &lt;a href=&quot;https://bobochen.dev/blog/context-engineering-deep-dive&quot;&gt;context engineering&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;它會亂做（→ 你寫了 &lt;a href=&quot;https://bobochen.dev/blog/spec-driven-development-for-agents&quot;&gt;spec&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;它可能搞壞東西（→ 你設了 &lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-testing-safety&quot;&gt;安全網&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;它很貴（→ 你做了 &lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-cost-optimization&quot;&gt;成本優化&lt;/a&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些風險可以用工程方法降低，但不能全部消除。法規、產品方向、供應商故障與未知未知，仍需要人與組織承擔。&lt;/p&gt;
&lt;p&gt;把這一節整理成一張圖。工程對策能降低已知風險，但消不掉的那部分不會消失，只會落到人與組織身上。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-future-and-you/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Agentic Engineering 不是盲目信任 AI，而是用工程方法建立有根據的信任。&lt;/p&gt;
&lt;p&gt;這整個系列教的，就是這件事。&lt;/p&gt;
&lt;h2 id=&quot;結語工程師的工作正在重新分配&quot;&gt;結語：工程師的工作正在重新分配&lt;/h2&gt;
&lt;p&gt;我不知道 5 年後工程師還需不需要寫 code。老實說，沒有人知道。&lt;/p&gt;
&lt;p&gt;我能確定的是，2026 年的工程師可以把更多實作、探索與驗證工作交給工具。對範圍清楚的小型任務，個人確實能做得比以前快；但 &lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-daily-workflow-advanced&quot;&gt;60 分鐘案例&lt;/a&gt;只是單次紀錄，不能拿來推論「一個人等於一個團隊」。營運、使用者研究、資安與長期維護也不會因 code 產生變快就消失。&lt;/p&gt;
&lt;p&gt;Builder 的起步門檻降低了，交付可靠產品的門檻沒有消失。一份好的 &lt;a href=&quot;https://bobochen.dev/blog/spec-driven-development-for-agents&quot;&gt;spec&lt;/a&gt;與 &lt;a href=&quot;https://bobochen.dev/blog/claude-md-rules-files-masterclass&quot;&gt;CLAUDE.md&lt;/a&gt;能減少 agent 猜測，卻仍需要人 review、測試與做取捨。&lt;/p&gt;
&lt;p&gt;Agent 會放大決策的執行速度：方向對時更快看到成果，方向錯時也更快擴大返工。這就是為什麼判斷與護欄變重要，而不是因為人類突然「不可取代」。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;所以，如果你問我「工程師還需要寫 code 嗎？」&lt;/p&gt;
&lt;p&gt;我的答案是：還需要。你不一定要親手輸入每一行，但必須保有讀懂、修改、測試與除錯的能力，同時知道為什麼要寫、寫到哪裡、出了事誰負責。&lt;/p&gt;
&lt;p&gt;前 14 篇從 &lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-what-is-it&quot;&gt;Agentic Engineering 是什麼&lt;/a&gt; 談到 &lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-team-adoption&quot;&gt;怎麼帶進團隊&lt;/a&gt;；後面兩篇再用 TaiwanSalary 與 agent loop 補上實際案例。它們不是放諸四海皆準的方法論，而是我一年使用經驗、官方資料與幾次失敗整理出的工作手冊。&lt;/p&gt;
&lt;p&gt;如果這個系列對你有幫助，它之後會整理成書。&lt;/p&gt;
&lt;p&gt;不是因為我要賣書，而是因為我相信，工程師需要一份「agent 使用手冊」——不是工具教學，而是方法論。&lt;/p&gt;
&lt;p&gt;這份手冊，目前就是這 16 篇。&lt;/p&gt;
&lt;h2 id=&quot;takeaway&quot;&gt;Takeaway&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Benchmark 要看資料集、harness 與時間點&lt;/strong&gt;。Verified 超過 70%、Pro 約 23% 是 2025 年發布時的跨資料集快照，不是今天可直接相減的能力差。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;需要保留的是 ownership&lt;/strong&gt;。Agent 可以參與需求、設計、協調與研究，但判斷、品味、組織承諾與倫理責任要有明確的人承擔。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;防衰退不是固定排一天禁用 AI&lt;/strong&gt;。定期確認自己仍能解釋、測試與 debug，再用 context、spec 與 review 練習把 agent 納入可靠流程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;下一篇：&lt;a href=&quot;https://bobochen.dev/blog/subagent-staged-build-taiwansalary&quot;&gt;一個 session、7 個 subagent：TaiwanSalary 分階段實作紀錄&lt;/a&gt;&lt;/em&gt;
&lt;em&gt;完整系列：&lt;a href=&quot;https://bobochen.dev/blog/agentic-engineering-what-is-it&quot;&gt;系列目錄&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.BaTdqDSD.webp" medium="image"/><category>Agentic Engineering</category><category>AI</category><category>未來趨勢</category><category>職涯</category><category>軟體工程</category><enclosure url="https://bobochen.dev/_astro/cover.BaTdqDSD.webp" length="0" type="image/png"/></item><item><title>Homebrew 6.0 來了：先搞懂這幾個會影響你日常的改動</title><link>https://bobochen.dev/blog/homebrew-6-0-0/</link><guid isPermaLink="true">https://bobochen.dev/blog/homebrew-6-0-0/</guid><description>Homebrew 6.0 不是炫技版號，而是把資安收緊、把你每天 brew install 的習慣改掉，還開始跟 Intel Mac 道別。挑幾個對 Mac 開發者最有感的改動，實測給你看。</description><pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;如果你是 Mac 開發者，&lt;code&gt;brew&lt;/code&gt; 大概是你每天最早敲下的幾個指令之一。所以當它跳上 &lt;strong&gt;6.0&lt;/strong&gt; 這個大版號時，值得花十分鐘搞清楚發生了什麼事——因為大版號意味著 breaking change，而這次有幾個改動，是你「下一次 &lt;code&gt;brew install&lt;/code&gt; 就會直接撞到」的那種。&lt;/p&gt;
&lt;p&gt;先給結論：6.0 的重點，是把你每天在用的 brew 變得&lt;strong&gt;更安全、也更快&lt;/strong&gt;——不是塞一堆新功能，而是把基本功做扎實。而你會直接撞到的，是這三件事——&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;把供應鏈安全收緊&lt;/strong&gt;：第三方 tap 不再「裝了就跑」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;改掉你每天的操作習慣&lt;/strong&gt;：&lt;code&gt;brew install&lt;/code&gt; 現在會先問你一句。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;開始跟 Intel Mac 道別&lt;/strong&gt;：x86_64 進入退場時間表。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;剩下的都是錦上添花。下面一個一個看。&lt;/p&gt;
&lt;h2 id=&quot;brew-install-之後它現在會先問你一句&quot;&gt;brew install 之後，它現在會先問你一句&lt;/h2&gt;
&lt;p&gt;這是你最快會注意到的改動。以前 &lt;code&gt;brew install something&lt;/code&gt; 按下 enter，它就一路裝到底。6.0 開始，&lt;strong&gt;對開發者預設開啟「ask 模式」&lt;/strong&gt;：你敲下指令後，它不會立刻動手，而是先列出這次會連帶安裝或升級哪些相依套件，停下來等你回一個 &lt;code&gt;Y&lt;/code&gt;。升級時如果沒有東西需要動，它也夠聰明、不會多問。&lt;/p&gt;
&lt;p&gt;官方說這是照使用者問卷做的決定——大家其實想在「它要對我的系統做什麼」之前，有個喊停的機會。&lt;/p&gt;
&lt;p&gt;實務上唯一要注意的是 &lt;strong&gt;CI&lt;/strong&gt;。你的 pipeline 裡如果有 &lt;code&gt;brew install&lt;/code&gt;，多了一個互動式確認就會直接卡住。兩個解法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 本機：這次不要問
brew install -y wget          # 或寫成 --no-ask

# CI：整個環境都別問

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
&lt;code&gt;-y&lt;/code&gt; / &lt;code&gt;--no-ask&lt;/code&gt; 是「這次不要問」，&lt;code&gt;HOMEBREW_NO_ASK&lt;/code&gt; 是「整個環境都別問」。本機留著 ask 模式其實很好用（避免手滑裝錯、被拖一大串相依套件下來），但 CI 記得設環境變數。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;冷知識那些看不懂的名詞其實是一整座酒廠&quot;&gt;冷知識：那些看不懂的名詞，其實是一整座酒廠&lt;/h2&gt;
&lt;p&gt;tap、keg、cask、bottle——這幾個字很多人用了好幾年還是背不起來。&lt;/p&gt;
&lt;p&gt;只要記住 homebrew 這個字本身的意思是「自釀酒」，整套就通了。它們是同一個釀酒比喻的不同零件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;formula&lt;/strong&gt;：配方，也就是「這支酒怎麼釀」的腳本&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tap&lt;/strong&gt;：酒龍頭，第三方的 formula 倉庫，所以叫「接一個 tap」&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bottle&lt;/strong&gt;：瓶裝，預先編譯好的二進位包，開瓶就能喝&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;keg&lt;/strong&gt;：小桶，單一套件實際安裝的位置&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cellar&lt;/strong&gt;：酒窖，所有 keg 放在一起的地方，也就是 &lt;code&gt;Cellar&lt;/code&gt; 那個目錄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cask&lt;/strong&gt;：木桶，用來裝 GUI 應用程式的延伸&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可愛歸可愛，也不是所有人都買單。Homebrew 的 GitHub 上有一張很正經的 issue 在提議把這些酒名全部換成講人話的版本，理由是「就算你用過別的套件管理器，也分不出 keg、tap、bottle、cask 的差別，而且從上下文根本猜不出來」。這張 issue 在 2023 年 3 月被 maintainer 以「不打算採納」結案並鎖定了——酒廠比喻活了下來。&lt;/p&gt;
&lt;p&gt;（來源：&lt;a href=&quot;https://docs.brew.sh/Formula-Cookbook#homebrew-terminology&quot;&gt;Homebrew 術語表&lt;/a&gt;、&lt;a href=&quot;https://github.com/Homebrew/brew/issues/14912&quot;&gt;Homebrew/brew issue #14912&lt;/a&gt;）&lt;/p&gt;
&lt;h2 id=&quot;第三方-tap-不再裝了就跑tap-信任機制&quot;&gt;第三方 tap 不再「裝了就跑」：Tap 信任機制&lt;/h2&gt;
&lt;p&gt;這是 6.0 真正的頭條，也是最值得花時間理解的一個。&lt;/p&gt;
&lt;p&gt;先講清楚問題：一個第三方 tap（&lt;code&gt;brew tap user/repo&lt;/code&gt; 那種）本質上是&lt;strong&gt;一包會在你機器上、用你的權限、不經沙箱執行的 Ruby 程式碼&lt;/strong&gt;。你 &lt;code&gt;brew install&lt;/code&gt; 它底下的某個 formula，等於同意跑它的安裝腳本。過去這件事是默默發生的——這也正是為什麼近期會出現「把惡意程式碼塞進 tap」這類攻擊。&lt;/p&gt;
&lt;p&gt;6.0 把這個洞補起來：&lt;strong&gt;第三方 tap（以及它底下的 formula、cask、外部指令）現在必須先被你明確信任，Homebrew 才會去 evaluate 它的程式碼。&lt;/strong&gt; 官方的 Homebrew taps 和內建指令一律預設信任，所以你日常的 &lt;code&gt;brew install wget&lt;/code&gt; 完全不受影響。&lt;/p&gt;
&lt;p&gt;要信任有兩種做法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 做法一（推薦）：只信任你真正要的那一個
# 用「完整路徑名」安裝，就只信任這一項
brew install user/repo/formula
brew install --cask user/repo/cask

# 做法二：信任整個 tap（連它之後新增的東西也一起放行）
brew trust user/repo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;「做法一」的範圍最小，是官方建議的預設姿勢——你要哪個就信任哪個，而不是把整個 tap 未來會長出來的所有東西，現在就先簽下去。&lt;/p&gt;
&lt;p&gt;過渡期如果你被擋住、或 CI 整批爆掉，有一個&lt;strong&gt;暫時&lt;/strong&gt;的逃生門：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但官方講得很白：這只是過渡用的，最終會強制。所以正解是把你常用的幾個 tap 一次信任好，而不是長期靠這個環境變數繞過。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
如果你的 CI 在升上 6.0 之後突然變紅，第一個要懷疑的就是這裡——&lt;code&gt;brew doctor&lt;/code&gt; 的 untrusted-tap 檢查、或第三方 tap 的 formula 載入被擋下。先盤點 pipeline 裡用到哪些非官方 tap，把它們補上 trust。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;把 ask 模式和 tap trust 放在一起看，新的安裝流程其實是一棵很清楚的判斷樹：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/homebrew-6-0-0/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;brew-exechomebrew-版的-npx&quot;&gt;brew exec：Homebrew 版的 npx&lt;/h2&gt;
&lt;p&gt;如果你寫過 Node，一定用過 &lt;code&gt;npx&lt;/code&gt;——不想把工具裝進全域，臨時拉下來跑一次就好。6.0 給了 Homebrew 一個對應的東西：&lt;strong&gt;&lt;code&gt;brew exec&lt;/code&gt;&lt;/strong&gt;（縮寫 &lt;code&gt;brew x&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;它的用法是：指定這次需要哪些 formula，Homebrew 幫你（必要時）裝好、把它們和相依套件的執行檔目錄塞進 &lt;code&gt;PATH&lt;/code&gt;，然後跑你的指令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 臨時用 jq + yq 跑一個腳本，不用先全域安裝
brew exec --formulae=jq,yq -- ./script.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;對「我只是想跑個一次性腳本、不想污染環境」這種情境很實用，尤其是寫教學、寫 Makefile、或想在乾淨環境裡驗證相依關係的時候。&lt;/p&gt;
&lt;h2 id=&quot;brew-vulns順手掃一下裝了什麼有洞的東西&quot;&gt;brew vulns：順手掃一下裝了什麼有洞的東西&lt;/h2&gt;
&lt;p&gt;延續這版的資安主軸，6.0 還多了 &lt;strong&gt;&lt;code&gt;brew vulns&lt;/code&gt;&lt;/strong&gt;：拿你「已經安裝」的套件去比對已知漏洞，告訴你哪些東西該升級了。&lt;/p&gt;
&lt;p&gt;過去你大概得靠額外工具、或自己留意 CVE，現在 Homebrew 本身就能給你一張清單。詳細選項可以用 &lt;code&gt;brew vulns --help&lt;/code&gt; 看，但概念很單純——把「你裝了什麼」和「哪些版本有已知問題」對起來，是個值得偶爾跑一次的習慣。&lt;/p&gt;
&lt;h2 id=&quot;這版整體變快了&quot;&gt;這版整體變快了&lt;/h2&gt;
&lt;p&gt;6.0 把幾個原本是選用的加速項目轉成預設：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;內建 JSON API 變預設&lt;/strong&gt;：把套件 metadata 合併成單次下載，少了一堆零碎的網路請求，啟動更快。（舊的 &lt;code&gt;HOMEBREW_USE_INTERNAL_API&lt;/code&gt; 環境變數也因此功成身退、被標記棄用。）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;brew leaves&lt;/code&gt; 快了約 30%&lt;/strong&gt;：就是列出你「手動裝、且沒被其他套件依賴」的那個指令。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;平行化&lt;/strong&gt;：升級時平行抓 bottle 的 tab、&lt;code&gt;brew bundle&lt;/code&gt; 預設平行安裝 formula。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這些都不用你動手，升上去就有。&lt;/p&gt;
&lt;h2 id=&quot;intel-mac-的倒數計時開始了&quot;&gt;Intel Mac 的倒數計時開始了&lt;/h2&gt;
&lt;p&gt;如果你還在用 Intel（x86_64）的 Mac，這段一定要看。官方公布了明確的退場時間表：&lt;/p&gt;

















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;時間&lt;/th&gt;&lt;th&gt;x86_64 macOS 的狀態&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;2026 年 9 月&lt;/td&gt;&lt;td&gt;降到 &lt;strong&gt;Tier 3&lt;/strong&gt;：不再跑 CI、不再產新的 bottle&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2027 年 9 月&lt;/td&gt;&lt;td&gt;&lt;strong&gt;完全不支援&lt;/strong&gt;，相關程式碼整批移除&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;白話講：今年九月之後，Intel Mac 上不少套件會開始沒有預編譯好的 bottle，得自己從原始碼編（慢，又容易踩雷）；明年九月之後就完全不在支援範圍內了。手上還有 Intel 機器在當生產力主力的人，這一年就是你規劃遷移的窗口。&lt;/p&gt;
&lt;p&gt;同一批 OS 相關更新還包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS 27（Golden Gate）&lt;/strong&gt; 初步支援上線（同時這版也是放掉 Intel 的版本）。&lt;/li&gt;
&lt;li&gt;認得 &lt;strong&gt;M5、M5 Pro/Max&lt;/strong&gt; 晶片。&lt;/li&gt;
&lt;li&gt;WSL 體驗改善（&lt;code&gt;brew config&lt;/code&gt; 會顯示 Windows build）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;其他順手知道就好的改動&quot;&gt;其他順手知道就好的改動&lt;/h2&gt;
&lt;p&gt;不是每個人都會用到，但掃一眼留個印象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cask 現在可以 pin&lt;/strong&gt;：把某個 GUI app 釘在特定版本，不讓它被升級。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Install Steps 框架&lt;/strong&gt;：把常見的安裝步驟（建目錄、搬檔案、建 symlink）改成「純資料、不需要在安裝當下跑 Ruby」的描述——又是一個縮小安全暴露面的設計。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下載冷卻（download cooldowns）&lt;/strong&gt;：對 Bundler、RubyGems、npm、pip、PyPI 加了節流，降低供應端被濫用的風險。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;brew bundle&lt;/code&gt; 變強&lt;/strong&gt;：新增 &lt;code&gt;npm&lt;/code&gt;、&lt;code&gt;krew&lt;/code&gt;、&lt;code&gt;winget&lt;/code&gt;（Windows）支援，&lt;code&gt;cleanup&lt;/code&gt; 也能一起管 npm／cargo／go／uv。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Services&lt;/strong&gt;：可以啟動 systemd timer、自動建好 service 需要的目錄。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;要現在升級嗎怎麼升&quot;&gt;要現在升級嗎？怎麼升&lt;/h2&gt;
&lt;p&gt;要。這版的安全改動是實打實有意義的，加速也是免費送的。升級照舊：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew update
brew upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;升完之後，給自己三個提醒：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;CI 會不會被卡&lt;/strong&gt;：ask 模式 → 設 &lt;code&gt;HOMEBREW_NO_ASK=1&lt;/code&gt;；第三方 tap → 補 &lt;code&gt;brew trust&lt;/code&gt;，別長期靠 &lt;code&gt;HOMEBREW_NO_REQUIRE_TAP_TRUST&lt;/code&gt; 繞過。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;盤點你的非官方 tap&lt;/strong&gt;：花五分鐘把常用的幾個一次信任好，之後就順了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Intel 使用者排遷移&lt;/strong&gt;：把「九月前」放進行事曆。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一句話總結：Homebrew 6.0 沒有給你新玩具，但它讓「你每天都在用的這個工具」更安全、更快，也更願意在動手前先問你一句。對天天 &lt;code&gt;brew&lt;/code&gt; 的人來說，這種版本反而最值得認真升。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;資料來源：&lt;a href=&quot;https://brew.sh/2026/06/11/homebrew-6.0.0/&quot;&gt;Homebrew 6.0.0 官方公告&lt;/a&gt;、&lt;a href=&quot;https://docs.brew.sh/Tap-Trust&quot;&gt;Homebrew Tap-Trust 文件&lt;/a&gt;、&lt;a href=&quot;https://docs.brew.sh/Manpage&quot;&gt;Homebrew Manpage&lt;/a&gt;。&lt;/em&gt;&lt;/p&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.DrXfWNUu.webp" medium="image"/><category>Homebrew</category><category>macOS</category><category>套件管理</category><category>開發環境</category><category>資安</category><category>考古</category><enclosure url="https://bobochen.dev/_astro/cover.DrXfWNUu.webp" length="0" type="image/png"/></item><item><title>第 6 章：買完才發現——.dev 網域居然不能收信</title><link>https://bobochen.dev/blog/domain-buying-lessons-06/</link><guid isPermaLink="true">https://bobochen.dev/blog/domain-buying-lessons-06/</guid><description>我以為買完 .dev 就結束了，直到要幫 App 設客服信箱才發現：買網域 ≠ 有信箱。還好 Cloudflare Email Routing 五分鐘免費搞定。</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;寫完前面五章，我以為這個系列已經收尾了——買域名的坑我都踩過了，建議也都給了，故事該結束了。&lt;/p&gt;
&lt;p&gt;結果這個系列又長出了第六章。而且這次的坑，是我自己親手挖的。&lt;/p&gt;
&lt;h2 id=&quot;背景給-app-買了一個漂亮的子網域&quot;&gt;背景：給 App 買了一個漂亮的子網域&lt;/h2&gt;
&lt;p&gt;最近我在幫自己的 Android App「AutoLaunch」做 landing page。這支 App 是排程自動啟動工具，準備上架 Google Play。&lt;/p&gt;
&lt;p&gt;域名我沒猶豫——直接用系列主角 &lt;code&gt;bobochen.dev&lt;/code&gt; 開了一個子網域：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;autolaunch.bobochen.dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子網域不用再買，DNS 加一筆 record 就有了，這正是我在第 4 章講的「多域名 vs 子網域策略」的好處。網站 deploy 上去、SSL 自動生效（&lt;code&gt;.dev&lt;/code&gt; 強制 HTTPS，這點以後再聊），一切順得不得了。&lt;/p&gt;
&lt;p&gt;我心想：「漂亮，這次學乖了，零成本擴充。」&lt;/p&gt;
&lt;p&gt;然後我開始填 Google Play 的上架資料，填到「&lt;strong&gt;開發者聯絡 Email&lt;/strong&gt;」那一欄——Play Console 要求一個能收信的客服信箱。&lt;/p&gt;
&lt;p&gt;我很自然地打上：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;support@autolaunch.bobochen.dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打完那一刻我愣住了：&lt;strong&gt;這個信箱……真的收得到信嗎？&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;發現過程買網域根本沒附信箱這回事&quot;&gt;發現過程：買網域根本沒附信箱這回事&lt;/h2&gt;
&lt;p&gt;我去查了一下，然後被自己的無知打了一巴掌。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;買域名，跟「擁有這個域名的 email」是兩件完全無關的事。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;當你註冊一個域名，你拿到的是「這串名字的所有權」跟「設定 DNS 的權利」。如此而已。它&lt;strong&gt;不附帶任何信箱伺服器&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;沒有人幫你架收信的伺服器（MX）&lt;/li&gt;
&lt;li&gt;沒有人幫你存信&lt;/li&gt;
&lt;li&gt;你在名片上印 &lt;code&gt;support@yourdomain.dev&lt;/code&gt;，別人寄過來，信會直接彈回去或石沉大海&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這不是 &lt;code&gt;.dev&lt;/code&gt; 的問題，是&lt;strong&gt;所有域名&lt;/strong&gt;都一樣。&lt;code&gt;.com&lt;/code&gt;、&lt;code&gt;.io&lt;/code&gt;、&lt;code&gt;.tw&lt;/code&gt; 通通沒附信箱。我只是因為 &lt;code&gt;.dev&lt;/code&gt; 看起來很「工程師」，下意識以為它什麼都包了。&lt;/p&gt;
&lt;p&gt;更尷尬的是：這是一個&lt;strong&gt;上架卡關&lt;/strong&gt;等級的問題。Google Play 那欄聯絡信箱不是裝飾品——審查被退、使用者客訴、政策通知，全部寄到那裡。如果我填一個收不到信的地址，等於把 App 的對外窗口直接關掉。&lt;/p&gt;
&lt;p&gt;那要怎麼辦？傳統解法是去買 Google Workspace（一個帳號月費，要養一個信箱）。但我只是要一個&lt;strong&gt;轉發用的客服信箱&lt;/strong&gt;，不需要一個完整的信箱帳號。殺雞用牛刀。&lt;/p&gt;
&lt;p&gt;後來我找到那把剛好的剪刀：&lt;strong&gt;Cloudflare Email Routing&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;解法cloudflare-email-routing免費且-5-分鐘快速搞定&quot;&gt;解法：Cloudflare Email Routing，免費且 5 分鐘快速搞定&lt;/h2&gt;
&lt;p&gt;我的域名 DNS 本來就託管在 &lt;a href=&quot;https://www.cloudflare.com/&quot;&gt;Cloudflare&lt;/a&gt;（這也是我第 4、5 章一直推薦的做法）。而 Cloudflare 有一個免費功能叫 &lt;strong&gt;Email Routing&lt;/strong&gt;，專門解決「我想要 &lt;code&gt;xxx@我的域名&lt;/code&gt; 但只想把信轉到現有 Gmail」這種需求。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;2025 年中 Cloudflare 換了新版 Email Routing UI&lt;/strong&gt;（舊版正在退場，進去可能會看到「You’re now using the new Email Routing UI」的提示）。網路上很多舊教學的截圖跟選單位置已經對不上，下面以&lt;strong&gt;新版介面&lt;/strong&gt;為準。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-分鐘快速上手&quot;&gt;3 分鐘快速上手&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;進入 Email Routing&lt;/strong&gt;
登入 Cloudflare → 選你的域名 → 左側選單 &lt;strong&gt;Compute → Email Service → Email Routing&lt;/strong&gt; → 點 &lt;strong&gt;Onboard Domain&lt;/strong&gt;。
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;確認要加的 DNS 記錄（一鍵自動）&lt;/strong&gt;
精靈會列出它要幫你寫進去的記錄——&lt;code&gt;MX&lt;/code&gt;（把收信路由到 Cloudflare）、一筆 &lt;code&gt;SPF&lt;/code&gt;(TXT) 和一筆 &lt;code&gt;DKIM&lt;/code&gt;(TXT)（讓轉發出去的信通過驗證）。直接按 &lt;strong&gt;Done&lt;/strong&gt;，Cloudflare 會自動寫好，你完全不用手刻 DNS。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;設定目的地信箱（你的 Gmail）&lt;/strong&gt;
切到 &lt;strong&gt;Destination Addresses&lt;/strong&gt; 分頁，新增你的 Gmail，例如 &lt;code&gt;mygmail@gmail.com&lt;/code&gt;。Cloudflare 會寄一封驗證信到那個 Gmail，點一下確認連結就完成綁定。
&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這一步是為了證明那個 Gmail 真的是你的，避免有人亂轉發給別人。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;建立轉發規則&lt;/strong&gt;
到 &lt;strong&gt;Routing Rules&lt;/strong&gt; 分頁 → &lt;strong&gt;Create routing rule&lt;/strong&gt; → 在 &lt;strong&gt;Email pattern&lt;/strong&gt; 輸入要收信的名稱（例如 &lt;code&gt;autolaunch&lt;/code&gt;）→ 右邊的網域下拉選你的域名 → 動作選 &lt;strong&gt;Send to an email&lt;/strong&gt;，指定剛剛驗證好的 Gmail：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;autolaunch@bobochen.dev  →  mygmail@gmail.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者懶人作法，開 &lt;strong&gt;Catch-all&lt;/strong&gt;，把 &lt;code&gt;*@bobochen.dev&lt;/code&gt;（任何地址）全部轉到你的 Gmail。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;動作那欄你會看到另一個選項 &lt;strong&gt;Send to a Worker&lt;/strong&gt;——那是把信丟進程式裡（Email Workers）處理，屬於進階玩法，純轉發用不到，本篇先略過（文末有延伸連結）。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;😅 &lt;strong&gt;這裡又踩到第二個坑：Email Routing 不吃子網域。&lt;/strong&gt; 我原本想用前面 Google Play 填的那個 &lt;code&gt;support@autolaunch.bobochen.dev&lt;/code&gt;，結果到了 &lt;strong&gt;Create routing rule&lt;/strong&gt; 這一步，&lt;strong&gt;Email pattern&lt;/strong&gt; 右邊的網域下拉&lt;strong&gt;只有 &lt;code&gt;bobochen.dev&lt;/code&gt;，根本選不到 &lt;code&gt;autolaunch.bobochen.dev&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;原因是 Email Routing 只在「&lt;strong&gt;zone&lt;/strong&gt;」層級運作——你加進 Cloudflare 的 &lt;code&gt;bobochen.dev&lt;/code&gt; 才是一個 zone，&lt;code&gt;autolaunch.bobochen.dev&lt;/code&gt; 只是它底下的一筆 DNS record，不是獨立 zone，所以收不了信。&lt;/p&gt;
&lt;p&gt;一句話記住：&lt;strong&gt;網站可以掛子網域，信箱只能掛根網域。&lt;/strong&gt; 我只好把客服信箱改成 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;（用前綴標記是哪支 App），再回頭把 Google Play 那欄一起改掉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;設定完，我寄了一封測試信到 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;，原本預期應該幾秒後 Gmail 就「叮」一聲收到了，但是…&lt;/p&gt;
&lt;h3 id=&quot;沒收到信照這個順序查我整個踩了一輪才搞懂&quot;&gt;沒收到信？照這個順序查（我整個踩了一輪才搞懂）&lt;/h3&gt;
&lt;p&gt;這一段是我自己卡了快一小時、來回測了七八封信換來的。我把它整理成一條除錯路徑——&lt;strong&gt;從「最權威的證據」往「最容易被忽略的人為設定」走&lt;/strong&gt;，照順序排除就好，不要一開始就亂改設定。&lt;/p&gt;
&lt;h4 id=&quot;第一步看-cloudflare-的-activity-log唯一權威證據&quot;&gt;第一步：看 Cloudflare 的 Activity Log（唯一權威證據）&lt;/h4&gt;
&lt;p&gt;新版有一個 &lt;strong&gt;Activity Log&lt;/strong&gt; 分頁，每封進來的信都記一筆 &lt;strong&gt;Result&lt;/strong&gt;。先看這個，再決定問題在哪一端：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Activity Log 的 Result&lt;/th&gt;&lt;th&gt;意思&lt;/th&gt;&lt;th&gt;往哪查&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;完全沒有記錄&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;信根本沒進到 Cloudflare&lt;/td&gt;&lt;td&gt;&lt;code&gt;MX&lt;/code&gt; 沒生效。&lt;code&gt;dig MX 你的域名&lt;/code&gt; 要看到 &lt;code&gt;route1/2/3.mx.cloudflare.net&lt;/code&gt;；新版若顯示 &lt;em&gt;DNS records: Not configured&lt;/em&gt;，回去把 onboarding 的 DNS 那步按完。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;&lt;code&gt;Dropped&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Cloudflare 收到了但沒轉&lt;/td&gt;&lt;td&gt;多半是&lt;strong&gt;目的地還沒驗證&lt;/strong&gt;（&lt;em&gt;Destination Addresses&lt;/em&gt; 要 &lt;code&gt;Verified&lt;/code&gt; 不是 &lt;code&gt;Pending&lt;/code&gt;），或&lt;strong&gt;規則沒生效&lt;/strong&gt;。&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;&lt;code&gt;Forwarded&lt;/code&gt; / &lt;code&gt;Delivered&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Cloudflare 已經把信交出去了，它沒問題&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;問題在收信端（Gmail）。往第二步走。&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;重點觀念：&lt;code&gt;Forwarded&lt;/code&gt; 不等於「你收件匣看得到」。&lt;/strong&gt; 它只保證 Cloudflare 把信交給了目的地的伺服器，之後信被收件端怎麼處理，Cloudflare 管不到。我卡最久就是誤把 &lt;code&gt;Forwarded&lt;/code&gt; 當成「應該要收到」。&lt;/p&gt;
&lt;h4 id=&quot;第二步result-是-forwarded-卻收不到--是收信端-gmail-的兩個陷阱&quot;&gt;第二步：Result 是 Forwarded 卻收不到 → 是收信端 Gmail 的兩個陷阱&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;陷阱 A：你「從目的地 Gmail 自己寄給自己」測&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我一開始用 &lt;code&gt;me@gmail.com&lt;/code&gt; 寄到 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;，而它又轉回&lt;strong&gt;同一個&lt;/strong&gt; &lt;code&gt;me@gmail.com&lt;/code&gt;。Gmail 會把「自己寄給自己」的信&lt;strong&gt;判定為重複、不在收件匣顯示&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;更坑的是：你去搜會發現「有一封欸」——但那其實是 Gmail 幫你留的 &lt;strong&gt;寄件備份（Sent）副本&lt;/strong&gt;，不是轉發進收件匣的那封。看到它會讓你誤以為「自寄會通、外部信才有問題」，整個判斷被帶偏。&lt;/p&gt;
&lt;p&gt;Cloudflare 知道這坑太多人踩，還會&lt;strong&gt;主動寄一封通知&lt;/strong&gt;：標題叫 &lt;strong&gt;「Missing email from …@gmail.com to …?」&lt;/strong&gt;，直接告訴你「信沒丟，是 Gmail 把自寄信去重了」。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;解法&lt;/strong&gt;：測試&lt;strong&gt;一律用「別的信箱」寄&lt;/strong&gt;（手機另一個帳號、請朋友、或 &lt;a href=&quot;https://www.mail-tester.com/&quot;&gt;mail-tester.com&lt;/a&gt; 順便驗 deliverability）。要翻出那封「消失」的自寄信，Gmail 搜 &lt;code&gt;in:anywhere&lt;/code&gt; 會在「所有郵件」找到它。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;陷阱 B：目的地那個 Gmail 帳號「自己」把信吃掉&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;排除自寄後我又踩第二層：改用一個&lt;strong&gt;完全外部的信箱&lt;/strong&gt;（例如 &lt;code&gt;someone@othercompany.com&lt;/code&gt;）寄，Activity Log 一樣 &lt;code&gt;Forwarded&lt;/code&gt;，但 &lt;code&gt;me@gmail.com&lt;/code&gt; 連 &lt;code&gt;in:anywhere&lt;/code&gt; 都搜不到。&lt;/p&gt;
&lt;p&gt;關鍵隔離測試：&lt;strong&gt;把轉發終點換成「不同供應商」&lt;/strong&gt;。我改寄到一個會轉到 &lt;strong&gt;Yahoo&lt;/strong&gt; 信箱的地址——&lt;strong&gt;秒收&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;外部信箱 → hi@bobochen.dev → Yahoo 信箱        ✅ 秒收
外部信箱 → autolaunch@bobochen.dev → 某 Gmail  ❌ 消失
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同一條 Cloudflare 規則、同一個寄件者，只差「終點信箱」就一個收得到、一個收不到——&lt;strong&gt;鐵證是那個 Gmail 帳號本身&lt;/strong&gt;在吃信，跟 Cloudflare 完全無關。&lt;/p&gt;
&lt;p&gt;而最後揪出來的真兇，是 Gmail 一個惡名昭彰的 &lt;strong&gt;POP 設定&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;設定 → 轉發和 POP/IMAP → POP 下載&lt;/strong&gt;，裡面有一個選項「&lt;strong&gt;當郵件透過 POP 存取後&lt;/strong&gt;」，如果它被設成 &lt;strong&gt;封存&lt;/strong&gt;或&lt;strong&gt;刪除 Gmail 副本&lt;/strong&gt;，那麼&lt;strong&gt;只要有任何 POP 用戶端連上來抓信，Gmail 就會把那封信從收件匣移走&lt;/strong&gt;（封存→進「所有郵件」、刪除→直接沒了）。Google 官方說明白紙黑字：選了 archive 或 delete，「&lt;em&gt;your messages will vanish from the inbox once your POP client accesses them&lt;/em&gt;」。&lt;/p&gt;
&lt;p&gt;所以真實的時序是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;別人寄信 → Cloudflare 轉發 → 確實送進收件匣（Activity Log: Forwarded ✅）
        → 幾秒後某個 POP 用戶端連上來把它抓走
        → 依「封存/刪除」設定，這封信從收件匣消失 ❌
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;我把 Gmail 的 POP 停用，沒有用戶端來抓信，信就乖乖留在收件匣了。&lt;/strong&gt; 信從頭到尾都有送達，只是送達後幾秒內被 POP 規則清掉。

這也回頭解釋了前面每一個詭異現象：&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;現象&lt;/th&gt;&lt;th&gt;真正原因&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Activity Log 寫 &lt;code&gt;Forwarded&lt;/code&gt; 卻收不到&lt;/td&gt;&lt;td&gt;信送達了，是&lt;strong&gt;送達之後&lt;/strong&gt;才被 POP 清走&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;自寄信「看得到」&lt;/td&gt;&lt;td&gt;看到的是 &lt;strong&gt;Sent 寄件備份&lt;/strong&gt;——POP 只抓收件匣新進信、不動 Sent，所以它留著（這就是當初把我帶偏的元兇）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;外部信消失&lt;/td&gt;&lt;td&gt;那是貨真價實的「新進收件信」，正好是 POP 會抓走的對象&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;轉到 Yahoo 秒收&lt;/td&gt;&lt;td&gt;Yahoo 沒有那個 POP 用戶端在背景抓信，不受影響&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;篩選器找半天找不到&lt;/td&gt;&lt;td&gt;因為根本不是篩選器，是 POP 的「下載後動作」&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;✅ &lt;strong&gt;解法&lt;/strong&gt;：把那個「當郵件透過 POP 存取後」改成 &lt;strong&gt;「在收件匣保留 Gmail 的副本」&lt;/strong&gt;（或乾脆停用 POP）。POP 要生效一定有個用戶端在連——可能是你&lt;strong&gt;另一個信箱用「檢查其他帳戶的郵件（POP3）」在匯入它&lt;/strong&gt;、某支舊手機郵件 App、或某個忘了的第三方服務；Gmail 網頁版&lt;strong&gt;最下面&lt;/strong&gt;的「上次帳戶活動 → 詳細資料」能看到是誰在 POP 連線。懶得追查的話，&lt;strong&gt;換一個乾淨的終點信箱&lt;/strong&gt;（Yahoo、Outlook、另一個沒掛 POP 的 Gmail）最快。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;一句話總結這條除錯路徑&quot;&gt;一句話總結這條除錯路徑&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;先看 Activity Log 的 Result 分「哪一端」&lt;/strong&gt;（沒記錄=DNS／Dropped=Cloudflare／Forwarded=收信端）→ &lt;strong&gt;Forwarded 卻收不到，幾乎都是收信端 Gmail&lt;/strong&gt;：先排除「自寄去重」，再用「換不同供應商的終點」隔離出是不是「那個帳號自己在吃信」——而帳號吃信最陰險的一種就是 &lt;strong&gt;POP 的『下載後封存/刪除』&lt;/strong&gt;。&lt;strong&gt;全程不要用『從目的地自己寄給自己』來測&lt;/strong&gt;，這是萬惡之源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;實際操作時，可以直接照這棵決策樹排除：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/domain-buying-lessons-06/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;整個過程大概 5 分鐘，&lt;strong&gt;$0&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;具體數據--結果&quot;&gt;具體數據 / 結果&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;項目&lt;/th&gt;&lt;th&gt;Google Workspace&lt;/th&gt;&lt;th&gt;Cloudflare Email Routing&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;費用&lt;/td&gt;&lt;td&gt;每帳號月費&lt;/td&gt;&lt;td&gt;&lt;strong&gt;免費&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;設定時間&lt;/td&gt;&lt;td&gt;要建帳號、設 DNS&lt;/td&gt;&lt;td&gt;&lt;strong&gt;約 5 分鐘&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;可用地址數&lt;/td&gt;&lt;td&gt;看方案&lt;/td&gt;&lt;td&gt;&lt;strong&gt;無限 alias / catch-all&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;DNS 設定&lt;/td&gt;&lt;td&gt;部分要手動&lt;/td&gt;&lt;td&gt;&lt;strong&gt;一鍵自動&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;適合場景&lt;/td&gt;&lt;td&gt;真的要一個獨立信箱&lt;/td&gt;&lt;td&gt;只要「收信轉發」&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;對一個獨立開發者、只想要客服信箱能收得到信的情境，後者完勝。&lt;/p&gt;
&lt;h2 id=&quot;反思&quot;&gt;反思&lt;/h2&gt;
&lt;h3 id=&quot;技術面收信和寄信是兩回事&quot;&gt;技術面：收信和寄信是兩回事&lt;/h3&gt;
&lt;p&gt;這次最重要的觀念釐清是——&lt;strong&gt;「收信」跟「寄信」是兩套機制，不要混為一談。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;收信&lt;/strong&gt;：靠 &lt;code&gt;MX&lt;/code&gt; 紀錄告訴全世界「寄到這個域名的信要送去哪台伺服器」。Cloudflare Email Routing 解決的是這一半。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寄信&lt;/strong&gt;：要讓別人相信「這封信真的是 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt; 寄的，不是冒名」，得設 &lt;code&gt;SPF&lt;/code&gt; / &lt;code&gt;DKIM&lt;/code&gt; / &lt;code&gt;DMARC&lt;/code&gt;，否則會被丟進垃圾信。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;還有一個 zone 的觀念：&lt;strong&gt;Email Routing 只認根網域（zone），不認子網域。&lt;/strong&gt; &lt;code&gt;bobochen.dev&lt;/code&gt; 是 zone，&lt;code&gt;autolaunch.bobochen.dev&lt;/code&gt; 只是它底下的 DNS record——所以信箱掛得上前者、掛不上後者。網站可以盡情用子網域，但要收信，地址得回到根網域。&lt;/p&gt;
&lt;p&gt;Email Routing 是&lt;strong&gt;單向轉發&lt;/strong&gt;——你能收，但預設不能用這個地址「寄」出去。如果之後我想用 Gmail 直接以 &lt;code&gt;autolaunch@...&lt;/code&gt; 的身分回信，還得另外做 Gmail 的「Send mail as」+ SMTP 設定；如果要程式化發送驗證信、電子報，那又是 Resend 之類服務的範疇。這部分我在下一章單獨寫了完整作法：&lt;a href=&quot;https://bobochen.dev/blog/domain-buying-lessons-07/&quot;&gt;第 7 章：信收得到了，但能用它「寄」信嗎？&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;但對「上架填一個收得到的客服信箱」這個當下的需求，免費轉發剛剛好。&lt;/p&gt;
&lt;h3 id=&quot;心態面上線前的隱形必備項最容易漏&quot;&gt;心態面：上線前的「隱形必備項」最容易漏&lt;/h3&gt;
&lt;p&gt;寫程式的時候，我們的注意力都在功能、UI、效能。但一個產品要真的「上線」，有一堆&lt;strong&gt;不寫 code 的隱形必備項&lt;/strong&gt;：能收信的客服信箱、隱私權政策頁、應用程式截圖、Data Safety 表單……&lt;/p&gt;
&lt;p&gt;這些東西平常不會出現在開發待辦清單裡，偏偏每一個漏掉都能讓你卡在上架的最後一哩路。我這次就是填表單填到一半才驚覺信箱問題——還好是現在發現，不是 App 被退件、使用者寄信來卻人間蒸發之後。&lt;/p&gt;
&lt;p&gt;教訓很簡單：&lt;strong&gt;上架前，把「對外窗口」都實際測一遍。&lt;/strong&gt; 信箱寄一封測試信、隱私權頁開無痕視窗點一次、截圖在不同手機看一眼。別假設它會動。&lt;/p&gt;
&lt;h3 id=&quot;有趣發現我又幫這個系列加了一章&quot;&gt;有趣發現：我又幫這個系列加了一章&lt;/h3&gt;
&lt;p&gt;最諷刺的是，這個系列叫「買域名的慘痛教訓」，我以為第 5 章「給想買域名的人的五個建議」已經把坑講完了。結果第六個坑，是在我自以為學乖、零成本開子網域之後，從一個我根本沒想過的角度冒出來的。&lt;/p&gt;
&lt;p&gt;域名的學習大概就是這樣——你以為畢業了，它總有辦法再教你一課。所以如果要我把第 5 章的「五個建議」補成第六個：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;建議六：買完域名別急著慶祝。先想清楚你要不要用它收信——要的話，去開 Cloudflare Email Routing，免費五分鐘，別等到要用的那天才發現它是個空殼。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.CtDdu0Hu.webp" medium="image"/><category>域名</category><category>Cloudflare</category><category>Email</category><category>教訓</category><category>Side Project</category><enclosure url="https://bobochen.dev/_astro/cover.CtDdu0Hu.webp" length="0" type="image/png"/></item><item><title>第 7 章：信收得到了，但能用它「寄」信嗎？</title><link>https://bobochen.dev/blog/domain-buying-lessons-07/</link><guid isPermaLink="true">https://bobochen.dev/blog/domain-buying-lessons-07/</guid><description>Cloudflare Email Routing 只能收信、不能寄。想用自己網域的地址回客訴，得補上「Send mail as + Resend」這一半——還有一個會讓整封信進垃圾桶的 SPF 雷。</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上一章我用 Cloudflare Email Routing 免費五分鐘搞定了客服信箱，&lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt; 寄一封測試信，Gmail 幾秒就「叮」一聲收到。我那時心想：完美，結案。&lt;/p&gt;
&lt;p&gt;結果隔幾天，第一封真實客訴進來了。我在 Gmail 裡按下「回覆」，打完字正要送出，眼角瞄到寄件者那一欄——&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;寄件人：mygmail@gmail.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不是 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;對方會收到一封「客服信箱寄出問題、卻是用某個陌生 Gmail 回覆」的信。整個品牌感瞬間漏氣。我才意識到：&lt;strong&gt;我只解決了「收」，根本還沒解決「寄」。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;發現過程email-routing-是單行道&quot;&gt;發現過程：Email Routing 是「單行道」&lt;/h2&gt;
&lt;p&gt;我回頭去翻 Cloudflare 的設定，想找「寄信」的開關——找不到。因為它根本沒有。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare Email Routing 的設計就是單向的：只負責「把寄到你網域的信轉進你的 Gmail」，不負責「讓你用這個網域地址寄出去」。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;為什麼？因為收信和寄信是兩套完全不同的機制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;收信&lt;/strong&gt;靠 &lt;code&gt;MX&lt;/code&gt; 紀錄——告訴全世界「寄到 &lt;code&gt;@bobochen.dev&lt;/code&gt; 的信要送去哪台伺服器」。Email Routing 解決的就是這一半，它把信收下來再轉發。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寄信&lt;/strong&gt;靠的是一台能對外送信的 &lt;strong&gt;SMTP 伺服器&lt;/strong&gt;，而且收件方還會反過來查 &lt;code&gt;SPF&lt;/code&gt; / &lt;code&gt;DKIM&lt;/code&gt; / &lt;code&gt;DMARC&lt;/code&gt;，確認「這封信真的有資格代表 &lt;code&gt;bobochen.dev&lt;/code&gt; 寄出」。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cloudflare Email Routing &lt;strong&gt;不提供對外 SMTP&lt;/strong&gt;。所以「寄」這一半，你得自己補。&lt;/p&gt;
&lt;p&gt;這件事在官方文件上其實分得很清楚，只是要看到才知道——收與寄是兩個不同的產品、不同的方案門檻：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;看清楚兩行的差別：&lt;strong&gt;Email Routing 是 Free and Paid plans（收信免費），Email Sending 要 Workers Paid plan（寄信要付費方案）&lt;/strong&gt;。上一章五分鐘搞定的爽感，只涵蓋上面那一行。&lt;/p&gt;
&lt;p&gt;換句話說，所謂「雙向」根本不是一個功能，而是&lt;strong&gt;兩個獨立機制拼起來&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;收信（上一章已完成）：
  別人 → autolaunch@bobochen.dev → Cloudflare MX → 轉進你的 Gmail

寄信（這一章要補）：
  你在 Gmail 以 autolaunch@bobochen.dev 的身分撰寫 → 經外部 SMTP 送出 → 對方
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;解法gmailsend-mail-as-resend&quot;&gt;解法：Gmail「Send mail as」+ Resend&lt;/h2&gt;
&lt;p&gt;我的需求很單純：在 Gmail 裡按回覆，對方收到的寄件者是 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;。不想離開 Gmail、不想另外養一個信箱介面。&lt;/p&gt;
&lt;p&gt;對應的標準作法是 &lt;strong&gt;Gmail 的「Send mail as」功能 + 一個交易型 email 服務的 SMTP&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我選 &lt;a href=&quot;https://resend.com/&quot;&gt;Resend&lt;/a&gt;。原因：對開發者友善、免費額度 3,000 封/月（客服回信綽綽有餘）、同時提供 SMTP 和 API，而且我部落格的電子報本來就在用它，生態一致。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不想用 Resend 也可以：&lt;a href=&quot;https://www.brevo.com/&quot;&gt;Brevo&lt;/a&gt;（免費 300 封/天）、Amazon SES（極便宜）、MailerSend、SMTP2GO 都行，設定邏輯一模一樣，只是 SMTP 主機和 DNS 記錄換成各自的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;3-分鐘快速上手&quot;&gt;3 分鐘快速上手&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Step 1：在 Resend 驗證你的網域&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;註冊 Resend → 左側 &lt;strong&gt;Domains&lt;/strong&gt; → &lt;strong&gt;Add Domain&lt;/strong&gt; → 填 &lt;code&gt;bobochen.dev&lt;/code&gt;（填&lt;strong&gt;根網域&lt;/strong&gt;，理由跟上一章一樣——&lt;code&gt;autolaunch.bobochen.dev&lt;/code&gt; 是子網域，不是獨立 zone）。&lt;/li&gt;
&lt;li&gt;Resend 會給你一組要加進 DNS 的記錄：一筆 &lt;code&gt;MX&lt;/code&gt;（給回信用，可選）、一筆 &lt;code&gt;SPF&lt;/code&gt;(TXT)、幾筆 &lt;code&gt;DKIM&lt;/code&gt;(CNAME/TXT)。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Step 2：把記錄加進 Cloudflare DNS（注意 SPF 不能重複）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;DKIM 直接照貼即可。&lt;strong&gt;SPF 是這整件事最容易爆的雷&lt;/strong&gt;——後面獨立一段講，先照做：把 Resend 的 &lt;code&gt;include&lt;/code&gt; 併進你「現有那一筆」SPF，不要新增第二筆。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3：在 Resend 拿 SMTP 憑證&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Resend → &lt;strong&gt;SMTP&lt;/strong&gt;（或 API Keys）→ 取得：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Host: smtp.resend.com
Port: 587 (TLS)
User: resend
Pass: &amp;#x3C;你的 API key&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Step 4：在 Gmail 設定「Send mail as」&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Gmail → ⚙️ → &lt;strong&gt;查看所有設定&lt;/strong&gt; → &lt;strong&gt;帳戶和匯入&lt;/strong&gt; → 「&lt;strong&gt;Send mail as / 以這個地址寄信&lt;/strong&gt;」→ &lt;strong&gt;新增另一個電子郵件地址&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;名稱填 &lt;code&gt;AutoLaunch 客服&lt;/code&gt;，地址填 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;，&lt;strong&gt;取消勾選&lt;/strong&gt;「視為別名」（這樣回信才會用對的身分）。&lt;/li&gt;
&lt;li&gt;SMTP 伺服器填上 Step 3 那組（&lt;code&gt;smtp.resend.com&lt;/code&gt; / &lt;code&gt;587&lt;/code&gt; / &lt;code&gt;resend&lt;/code&gt; / API key）。&lt;/li&gt;
&lt;li&gt;Gmail 會寄一封驗證碼到 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;——因為上一章的 Email Routing 已經會把它轉進你的 Gmail，所以你&lt;strong&gt;在同一個收件匣就能拿到驗證碼&lt;/strong&gt;，貼上去完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;設定完，撰寫新信或回覆時，寄件者下拉就能選 &lt;code&gt;autolaunch@bobochen.dev&lt;/code&gt;。收→寄的迴圈這才真正閉合。&lt;/p&gt;
&lt;p&gt;把收信、SMTP 與 DNS 驗證放在同一張圖裡，完整設定順序會清楚很多：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/domain-buying-lessons-07/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🔍 &lt;strong&gt;Step 4 卡在「收不到驗證碼」？&lt;/strong&gt; 那其實是「收信」沒通，不是寄信的問題——而且我光這一步就卡了快一小時。除錯順序我整理在上一章：先看 Cloudflare &lt;strong&gt;Activity Log&lt;/strong&gt; 的 Result（&lt;code&gt;Forwarded&lt;/code&gt; 代表 Cloudflare 沒事、問題在 Gmail 端），再排除兩個 Gmail 陷阱：&lt;strong&gt;①「從目的地自己寄給自己」會被去重&lt;/strong&gt;、&lt;strong&gt;② 目的地帳號的 POP「下載後封存/刪除」設定把信吃掉&lt;/strong&gt;（信送達後幾秒被某個 POP 用戶端清走；換一個不同供應商的終點信箱就能隔離）。完整除錯路徑見 &lt;a href=&quot;https://bobochen.dev/blog/domain-buying-lessons-06/#%E6%B2%92%E6%94%B6%E5%88%B0%E4%BF%A1%E7%85%A7%E9%80%99%E5%80%8B%E9%A0%86%E5%BA%8F%E6%9F%A5%E6%88%91%E6%95%B4%E5%80%8B%E8%B8%A9%E4%BA%86%E4%B8%80%E8%BC%AA%E6%89%8D%E6%90%9E%E6%87%82&quot;&gt;第 6 章：沒收到信？照這個順序查&lt;/a&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;那個會讓整封信進垃圾桶的-spf-雷&quot;&gt;那個會讓整封信進垃圾桶的 SPF 雷&lt;/h2&gt;
&lt;p&gt;這是我覺得最值得單獨拉出來講的一段，因為它&lt;strong&gt;不會報錯，只會默默讓你的信進垃圾桶&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;規則是：&lt;strong&gt;一個網域只能有「一筆」SPF 記錄。&lt;/strong&gt; 多筆會直接讓 SPF 驗證失效。&lt;/p&gt;
&lt;p&gt;問題來了——上一章啟用 Email Routing 時，Cloudflare 已經幫你寫了一筆 SPF：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;v=spf1 include:_spf.mx.cloudflare.net ~all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;現在 Resend 又要你加一筆它的 SPF。如果你&lt;strong&gt;手滑新增成第二筆&lt;/strong&gt;，兩筆 SPF 並存，驗證就壞了。正確做法是把兩個 &lt;code&gt;include&lt;/code&gt; &lt;strong&gt;合併進同一筆&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;v=spf1 include:_spf.mx.cloudflare.net include:amazonses.com ~all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（Resend 底層走 Amazon SES，所以 include 是 &lt;code&gt;amazonses.com&lt;/code&gt;；以 Resend 後台實際顯示的為準。）&lt;/p&gt;
&lt;p&gt;加上 DKIM 和 DMARC，寄信的「信任三件套」才齊：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SPF&lt;/strong&gt;（上面那筆，合併版）——授權哪些伺服器能代表你網域寄信。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DKIM&lt;/strong&gt;——用 Resend 給的 CNAME/TXT 記錄，幫每封信加數位簽章，證明沒被竄改。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DMARC&lt;/strong&gt;——加一筆 &lt;code&gt;_dmarc&lt;/code&gt; TXT。最保守的起手式是先用 &lt;code&gt;p=none&lt;/code&gt; 觀察，確認對齊後再收緊：
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;_dmarc  TXT  &quot;v=DMARC1; p=none; rua=mailto:autolaunch@bobochen.dev&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;新網域其實可以更大膽——直接把 &lt;code&gt;p&lt;/code&gt; 設嚴一點。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;p=none&lt;/code&gt; 的保守哲學是「先觀察、別誤擋信」，那是給&lt;strong&gt;舊網域&lt;/strong&gt;用的——已經用很久、你也搞不清楚還有哪些系統在拿這個網域寄信，貿然收緊怕把正常信也擋掉。但你手上這顆是&lt;strong&gt;全新買來&lt;/strong&gt;的：根本沒有任何歷史寄信流量會被誤殺，那就沒理由留一扇後門給冒名者。只要先確認自己的 SPF / DKIM 真的對齊（用 &lt;code&gt;rua&lt;/code&gt; 報告或 mail-tester 驗一遍），就可以一開始直接上 &lt;code&gt;p=reject&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;_dmarc  TXT  &quot;v=DMARC1; p=reject; rua=mailto:autolaunch@bobochen.dev&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;p=reject&lt;/code&gt; 是最強等級——任何冒名 &lt;code&gt;bobochen.dev&lt;/code&gt; 的信，收件方伺服器會「直接拒收」，連垃圾桶都不進，是防釣魚／冒名最有效的一道。&lt;code&gt;bobochen.dev&lt;/code&gt; 我自己就是這樣設的。唯一前提是對齊要先綠燈，否則會連自己的信一起擋掉；真不放心，就先用 &lt;code&gt;p=quarantine&lt;/code&gt;（冒名信丟垃圾桶、但不退信）過渡，觀察幾天 &lt;code&gt;rua&lt;/code&gt; 報告沒問題再升 &lt;code&gt;p=reject&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;三件套都綠燈，你的客服回信才不會被 Gmail / Outlook 丟進垃圾桶。設定完，去 &lt;a href=&quot;https://www.mail-tester.com/&quot;&gt;mail-tester.com&lt;/a&gt; 寄一封測，拿到 10/10 再上線。&lt;/p&gt;
&lt;h2 id=&quot;那如果是程式要自動寄信&quot;&gt;那如果是程式要自動寄信？&lt;/h2&gt;
&lt;p&gt;如果你的需求不是「人在 Gmail 點回覆」，而是 &lt;strong&gt;App 後端要自動發驗證信、通知、電子報&lt;/strong&gt;，那就不走 Gmail，直接用 API：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Resend API&lt;/strong&gt;（同一個帳號，呼叫一個 endpoint 就送出）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloudflare Email Sending / Email Workers&lt;/strong&gt;（信件邏輯直接跑在 Worker 裡）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這屬於另一個主題——Email Workers 怎麼程式化處理來信、transactional email 怎麼用 D1 + Resend 自己接——之後會另外寫。&lt;/p&gt;
&lt;h2 id=&quot;具體數據--結果&quot;&gt;具體數據 / 結果&lt;/h2&gt;



































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;項目&lt;/th&gt;&lt;th&gt;只用 Email Routing&lt;/th&gt;&lt;th&gt;補上 Resend 後&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;收信&lt;/td&gt;&lt;td&gt;✅ 免費轉發&lt;/td&gt;&lt;td&gt;✅ 免費轉發&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;寄信（以網域身分）&lt;/td&gt;&lt;td&gt;❌ 做不到&lt;/td&gt;&lt;td&gt;✅ Gmail 直接回&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;操作介面&lt;/td&gt;&lt;td&gt;Gmail&lt;/td&gt;&lt;td&gt;還是 Gmail（同一個）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;額外成本&lt;/td&gt;&lt;td&gt;$0&lt;/td&gt;&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;（Resend 免費 3,000 封/月）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;需要設定&lt;/td&gt;&lt;td&gt;MX + SPF&lt;/td&gt;&lt;td&gt;多加 DKIM + DMARC + SMTP&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;一樣是 $0，只是多花十分鐘設 DNS 和 Gmail。對「能收也能回的客服信箱」這個需求，剛好夠用。&lt;/p&gt;
&lt;h2 id=&quot;反思&quot;&gt;反思&lt;/h2&gt;
&lt;h3 id=&quot;技術面收和寄永遠是兩件事&quot;&gt;技術面：「收」和「寄」永遠是兩件事&lt;/h3&gt;
&lt;p&gt;這一章其實是上一章那句「收信和寄信是兩套機制」的具體下場。我上一章寫的時候是當理論講，這一章是真的被它咬了一口。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;收信免費又簡單&lt;/strong&gt;（MX 一鍵），會讓你誤以為「信箱搞定了」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;寄信才是真正麻煩的那一半&lt;/strong&gt;——不是技術難，是 deliverability（能不能不進垃圾桶）難，而它全押在 SPF / DKIM / DMARC 這三筆你看不到效果的 DNS 記錄上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;還有上一章那個「Email Routing 只認根網域（zone）」的限制，在 Resend 驗證網域時&lt;strong&gt;再次出現&lt;/strong&gt;：你驗證的是 &lt;code&gt;bobochen.dev&lt;/code&gt; 這個 zone，不是子網域。同一個觀念，換個場景又來敲你一次。&lt;/p&gt;
&lt;h3 id=&quot;心態面免費方案的邊界要先搞懂別等出事才補&quot;&gt;心態面：免費方案的「邊界」要先搞懂，別等出事才補&lt;/h3&gt;
&lt;p&gt;免費工具最危險的不是它要錢，是它&lt;strong&gt;只做一半、而你以為它做了全部&lt;/strong&gt;。Email Routing 把「收信」做得又快又免費，反而讓我輕忽了「寄信」根本不在它的守備範圍。&lt;/p&gt;
&lt;p&gt;更陰險的是 deliverability：信進垃圾桶&lt;strong&gt;不會跳任何錯誤&lt;/strong&gt;，你自己測試（寄給自己）還常常正常，等到真實客戶收不到、客訴你「都沒回我」，才發現 DMARC 沒設好。這種「沉默的失敗」最該在上線前主動驗一遍——所以我才會押著自己去 mail-tester 跑到 10/10 才收工。&lt;/p&gt;
&lt;h3 id=&quot;有趣發現這個系列又長出一章&quot;&gt;有趣發現：這個系列又長出一章&lt;/h3&gt;
&lt;p&gt;上一章結尾我才自嘲「我又幫這個系列加了一章」，沒想到一語成讖——光是一個客服信箱，就拆成了「收」（第 6 章）和「寄」（第 7 章）兩章。&lt;/p&gt;
&lt;p&gt;域名這條路大概就是這樣，你以為閉環了，它總有辦法讓你發現環只接了一半。所以如果第 5 章的「五個建議」要再補一條：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;建議七：信箱別只測「收得到」，也要測「寄得出、且不進垃圾桶」。收信靠 Email Routing 免費搞定，寄信要另外接 SMTP（Resend 之類）並設好 SPF / DKIM / DMARC——而且 SPF 全網域只能有一筆，記得用 &lt;code&gt;include&lt;/code&gt; 合併，別開第二筆。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.DWUPVE6C.webp" medium="image"/><category>域名</category><category>Cloudflare</category><category>Email</category><category>Resend</category><category>Side Project</category><enclosure url="https://bobochen.dev/_astro/cover.DWUPVE6C.webp" length="0" type="image/png"/></item><item><title>chezmoi 到底怎麼念？它是法文「我家」，拿來管 $HOME 剛剛好</title><link>https://bobochen.dev/blog/chezmoi-name-pronunciation-chez-moi/</link><guid isPermaLink="true">https://bobochen.dev/blog/chezmoi-name-pronunciation-chez-moi/</guid><description>chezmoi 不念「切斯莫伊」——它是法文 chez moi [ʃɛ mwa]「我家」。一個管理你 $HOME 的工具，名字就叫「我家」，背後是個很巧妙的設計隱喻。</description><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我寫了一整個系列在講 &lt;code&gt;chezmoi&lt;/code&gt;——多機同步、template、加密、private repo bootstrap……每天 &lt;code&gt;chezmoi apply&lt;/code&gt; 敲得飛快。&lt;/p&gt;
&lt;p&gt;但前陣子被同事問了一句話，我居然答不出來：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;「這個 &lt;code&gt;chezmoi&lt;/code&gt;，到底怎麼念？」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我下意識念了「切斯-莫伊」（chez-moy），唸完自己都心虛。回去查了一下才發現——&lt;strong&gt;大部分人都念錯了，包括我&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&quot;正解它是法文&quot;&gt;正解：它是法文&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;chezmoi&lt;/code&gt; 不是某個工程師亂拼的字母組合，它是一個&lt;strong&gt;法文片語&lt;/strong&gt; &lt;code&gt;chez moi&lt;/code&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE] chezmoi 怎麼念
&lt;strong&gt;&lt;code&gt;chez moi&lt;/code&gt; → IPA &lt;code&gt;[ʃɛ mwa]&lt;/code&gt;，注音近「雪・ㄇㄨㄚ」&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chez&lt;/code&gt; ≈ 「雪」&lt;code&gt;[ʃɛ]&lt;/code&gt;（不是「切斯」，z 不發音）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;moi&lt;/code&gt; ≈ 「ㄇㄨㄚ」&lt;code&gt;[mwa]&lt;/code&gt;（不是「莫伊」，oi 在法文念 &lt;code&gt;[wa]&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（注音是為了好記的近似標註，真正的母音還是以 IPA 為準。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;連 Google 搜尋的 AI 摘要都這樣標——唸「雪・ㄇㄨㄚ」。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;不用只信搜尋結果，官方文件首頁自己就標了音，還附三個發音檔：&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;有趣的是官方寫的是 &lt;code&gt;/ʃeɪ mwɑ/ (shay-mwa)&lt;/code&gt;，第一個母音用的是英語的 &lt;code&gt;shay&lt;/code&gt;（近「謝」），而不是法文原音的 &lt;code&gt;[ʃɛ]&lt;/code&gt;（近「雪」）——那是給英語使用者的近似標註。要唸得像法國人就照 &lt;code&gt;[ʃɛ mwa]&lt;/code&gt;，要跟英語圈的人溝通講 shay-mwa 也不會被糾正。反正兩者都不是「切斯莫伊」。&lt;/p&gt;
&lt;p&gt;關鍵就在那兩個「以為會發音、其實不發」的字母：&lt;code&gt;chez&lt;/code&gt; 的 &lt;code&gt;z&lt;/code&gt; 是啞音，&lt;code&gt;moi&lt;/code&gt; 的 &lt;code&gt;oi&lt;/code&gt; 在法文一律念 &lt;code&gt;[wa]&lt;/code&gt;（跟 &lt;code&gt;bonjour&lt;/code&gt; 旁邊的 &lt;code&gt;moi&lt;/code&gt;、&lt;code&gt;voilà&lt;/code&gt; 的邏輯一樣）。所以整個字聽起來是輕快的「&lt;strong&gt;雪・ㄇㄨㄚ&lt;/strong&gt;」，不是硬邦邦的「切斯莫伊」。&lt;/p&gt;
&lt;h2 id=&quot;意思更妙它是我家&quot;&gt;意思更妙：它是「我家」&lt;/h2&gt;
&lt;p&gt;把這個片語拆開：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chez&lt;/code&gt; 是法文的介系詞，意思是「&lt;strong&gt;在……的地方 / 在某人家&lt;/strong&gt;」&lt;/li&gt;
&lt;li&gt;&lt;code&gt;moi&lt;/code&gt; 就是「&lt;strong&gt;我&lt;/strong&gt;」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;合起來，&lt;code&gt;chez moi&lt;/code&gt; = &lt;strong&gt;「在我家 / 我家」&lt;/strong&gt;。這是法國人天天掛在嘴邊的日常用語——「Viens chez moi（來我家）」、「Je reste chez moi（我待在家）」。&lt;/p&gt;
&lt;h2 id=&quot;為什麼這名字是神來一筆&quot;&gt;為什麼這名字是神來一筆&lt;/h2&gt;
&lt;p&gt;現在回頭看這個工具在做什麼，你會會心一笑。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/chezmoi-dotfiles-multi-machine-setup/&quot;&gt;chezmoi 的設計哲學&lt;/a&gt;是：&lt;strong&gt;你的 dotfiles 是「原始碼」，你的 home 目錄是「編譯結果」&lt;/strong&gt;。它把 source state 編譯、渲染、解密之後，寫進你的 &lt;strong&gt;destination state——也就是你的 &lt;code&gt;$HOME&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;換句話說，這個工具&lt;strong&gt;從頭到尾就只做一件事：把東西安頓進你的家目錄&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;從「原始碼」到「我家」的路徑，可以濃縮成這張圖：repo 裡保存的是可跨機器維護的 source state，chezmoi 依當下這台機器的條件處理後，才寫成真正會被程式讀取的檔案。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bobochen.dev/blog/chezmoi-name-pronunciation-chez-moi/&quot;&gt;（本段有一張流程圖，請見原文）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;這也是命名最漂亮的地方：&lt;code&gt;chezmoi&lt;/code&gt; 管理的不是一份抽象備份，而是把同一套來源整理成每台電腦各自可住的「家」。&lt;/p&gt;
&lt;p&gt;而它的名字，字面意思就是「&lt;strong&gt;我家&lt;/strong&gt;」。&lt;/p&gt;
&lt;p&gt;一個專門管理 &lt;code&gt;$HOME&lt;/code&gt; 的工具，取名叫「我家」。連 Unix 裡代表 home 的那個 &lt;code&gt;~&lt;/code&gt; 符號都跟著一起呼應了。命名跟功能扣得這麼緊，實在漂亮。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP] 順帶一提
chezmoi 的作者是 &lt;a href=&quot;https://github.com/twpayne/chezmoi&quot;&gt;Tom Payne（twpayne）&lt;/a&gt;。下次有人把它念成「切斯莫伊」，你就可以淡淡地補一句：「是『雪・ㄇㄨㄚ』喔，法文，意思是我家。」&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;一句話記法&quot;&gt;一句話記法&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;念「雪・ㄇㄨㄚ」，意思是「我家」——管你 &lt;code&gt;$HOME&lt;/code&gt; 的工具，名字就叫我家。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;記住這句，發音、拼字、設計理念，一次全到位。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;想真的把「我家」搬上每一台新電腦，看這幾篇實戰：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bobochen.dev/blog/chezmoi-dotfiles-multi-machine-setup/&quot;&gt;chezmoi 實戰：一份 dotfiles 管理三台不同用途的 Mac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Dotfiles 管理術：用 chezmoi 一鍵重建開發環境&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bobochen.dev/blog/mackup-vs-chezmoi-vs-defaults-sh-comparison/&quot;&gt;Mackup vs chezmoi vs 手寫 script：macOS 設定備份工具怎麼選？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><media:content url="https://bobochen.dev/_astro/cover.Buq0kwgH.webp" medium="image"/><category>chezmoi</category><category>dotfiles</category><category>冷知識</category><category>macOS</category><category>開發環境</category><enclosure url="https://bobochen.dev/_astro/cover.Buq0kwgH.webp" length="0" type="image/png"/></item></channel></rss>