這週跨五個系統挖出同一類問題:功能骨架完整、測試沒報錯、UI 顯示正常——但真實流程從來沒走通過。有的是推測 selector 從沒對應過真實介面,有的是 session 管理以錯誤的粒度做索引,有的是 Next.js 頁面在建置期靜態化後,執行期狀態根本寫不進去。這份週報記錄我們怎麼一次次把「看起來完整」逼成「實際可用」。
以「專案名」還是「session id」作索引鍵,差的不只是一個欄位 #
Claude Nexus 的 chat_server 長期用「專案名」作停放與接管的索引鍵。表面上這讓「同一個專案繼續剛才的對話」成為可能,但只要同一個專案同時跑兩個對話,麻煩就來了:關掉對話 A 可能把對話 B 的停放位搶走,Operations 面板的孤兒清除邏輯也會誤殺仍在活躍的 session。根本矛盾在於「專案」是容器,「session」是個體,以容器為鍵無法表達「一個專案同時有多個平行對話」。
修法直接:改用 session id 作索引鍵,停放、接管、終止、逾時清理全部對齊到個體粒度。Close 對無回應 client 加了三秒硬逾時,防止一個壞連線卡住其他 session 的清理路徑。代價是「接管不再透明」——舊的做法可以不帶 id 直接認領同專案的任何停放,現在必須明確帶 session id。換來的是完全的隔離性:不同對話之間的生命週期互不干擾,Operations 面板的清除邏輯也不再有誤判空間。
「以容器為索引鍵」的設計常見於早期系統:命名簡單、概念自然,但一旦同容器允許多個實例並存,它就會開始出錯。凡是狀態管理用「類別名稱」或「群組名稱」當鍵的地方,都值得問一句:這個群組現在會不會同時有兩個活著的個體?
靜默降級比直接報錯更難修——Opus 5 安全分類器觸發的排查教訓 #
某台機器上出現「session 中途變笨、大量 turns 耗在 thinking 卻沒輸出」的症狀,起初完全無法歸因。行為退化不是錯誤訊息,看起來像是「模型狀態不穩定」,實際上是安全分類器擋了請求兩次之後,CLI 自動把整場對話靜默切換到舊版模型——scope 是 session,後續一百多則全黏在舊模型上。
從 JSONL 翻出 model_refusal_fallback 事件才確認根因。解法是在設定檔加環境變數停用 fallback,讓分類器擋了就回明確錯誤而非靜默降級。這改動本身很小,但診斷邏輯很重要:使用者看到「AI 變笨了」,不是「AI 說不行」,兩者的排查路徑完全不同。明確拒絕讓使用者能改提問或換模型;靜默降級讓使用者帶著錯誤假設繼續操作,問題以更扭曲的形式累積。
系統整合時,靜默的 fallback 機制(降級模型、切備援、吞錯誤重試)往往出於好意設計,目標是讓流程不中斷。但在需要人工介入的場景,讓流程「看起來繼續了」比直接停下來回報錯誤的傷害更大。設計降級時值得問:這個場景,使用者需要知道降級發生了嗎?
推測式實作的代價:eMobile 密碼輪換功能整修紀實 #
eMobile 的密碼到期自動切換備援組功能早在八月就已存在,UI 顯示使用中/備援標記,兩套系統都有,骨架看起來完整。但流程從來沒成功執行過:核心函式是依平台慣例「猜」出來的——把「舊密碼欄」當成必填前置判斷,一找不到就直接 return false。而那個平台根本沒有舊密碼欄,加上新密碼欄位的 selector 全錯、送出按鈕名稱不符、缺少關閉系統訊息 modal 的步驟。
取得客戶實機操作文件後,逐一對照校正所有 selector 並加入 modal 處理邏輯,HQ 與 B2B 兩套同步修正,16 項驗證全過。這個案例清楚示範了「推測式實作」的風險:在沒有真實目標介面的情況下,憑借「平台通常長這樣」寫出的邏輯,不是八九不離十,而是大概率完全不對。真正有效的驗收不是「程式碼寫完」,而是「實際打過一次完整流程」。
凡是串接外部系統的功能(政府平台、第三方 API、網頁自動化),骨架完整不代表流程走得通。預防方式只有一個:盡早取得真實環境的操作文件或帳號,在有真實目標的情況下寫第一行程式碼,而不是寫完再去對照。
Next.js 靜態渲染與執行期狀態的時序衝突:活動鎖定功能的兩次設計 #
抽獎活動系統需要一個「鎖定前台」功能:活動開放前,一般訪客只看到等候頁,無法存取任何內容或 API。第一版部署後正式站 HTTP 500。問題在於 Next.js 在建置期已把頁面預渲染成靜態 HTML,而鎖定狀態是執行期才切換的後台參數——靜態頁在執行期無法讀取 cookie 中的鎖定信號,兩者時序根本不同步。
發現問題後立即回滾,重新設計改以 middleware 層攔截所有前台路由,在邊緣執行層而非頁面元件內判斷鎖定狀態,確保無論頁面是靜態或動態都能被攔截。同時驗證了原始碼不洩漏任何頁面內容,前台 API(登錄、查詢、條碼解析)一律回 503,後台管理介面完全不受影響。後台新增開關,管理員可即時切換狀態並自訂等候頁文案。
Next.js 的靜態化優化是效能利器,但凡是「狀態由執行期決定」的邏輯(登入狀態、功能開關、A/B 測試),都不適合放在頁面元件層處理——應提到 middleware 或 edge function 層,在渲染之前就攔截。這個原則在任何支援預渲染的框架(Astro、Nuxt、SvelteKit)都同樣適用。
本週其他進展 #
- FIFA 2026 賽後站靜態化,效能大幅提升 — 世足結賽後即時代理容器仍持續運作,換成 nginx 純餵靜態檔後,回應時間從約 1.9 秒降至 0.2 秒以下,記憶體從 81 MB 降至 12.8 MB,外部 API 呼叫歸零,網址與外觀不變,所有已建立的引用連結全部維持有效。
- gomylife 每日新聞 SEO 題材來源轉向台灣受眾 — GSC 資料確認「日期+AI 動態」型文章主要吸引非目標受眾,決策改以財政部電子發票公告、iThome 等台灣來源為題材,標題改為主題導向,9/18 起新版重新開放收錄。
- gomylife 發布跨機器 AI 派工方法論文章 — 整理實際操作中歸納的派工原則,核心觀點「不要把資料搬過來,把工作派到有脈絡的地方去」,含失敗案例與邊界設計分析,約 4,000 字,已發布於 motoway.tw。
- 台股交易系統賣出路徑統一修正 — 釐清三條賣出路徑的混淆——收盤後送出市價單的路徑確認從未真實觸發,已修正為建立隔日 scheduled SELL 計畫走同一套自動執行路徑,賣出邏輯統一到有效時段內。
- 券商 SDK GIL 阻塞問題改以主機存活預檢繞過 — SDK 登入會搶佔 GIL 逾兩分鐘,背景執行緒方案實測仍阻塞超過 70 秒確認無效;改為 SDK 呼叫前先做 TCP 探測(3 秒逾時),主機不可達直接進 cooldown 並切換備援資料源,不觸碰 SDK。
- eMobile 發票作廢前置 gate 放寬 success 狀態 — 客戶常遇當天開立、當天需要作廢的場景,但發票停在 success 狀態(Turnkey 已送出、平台批次尚未確認)會被攔截。評估後只放寬 success 這一個狀態,HQ 與 B2B 各 4 處(作廢、註銷、折讓開立、折讓作廢)同步修改。
- eMobile 補印發票功能實作上架 — 法規要求補印發票必須標記「補印」字樣,此功能本週版本完成實作並打包上架。
- 太陽能監控資料更新時間戳顯示修復 — 右上角更新時間功能因前端只存 result.data 而遺漏 update_time 欄位,Badge 從未顯示過;修復後加入「幾秒前」自走 tick 與顏色警示(≤90 秒灰/90~180 秒黃/>180 秒紅),讓用戶能判斷資料是否過期。
- 太陽能電池安時數完成五路交叉驗證 — 透過觸底電壓、第二次零點、已知放電量反推、靜置電壓分布、BMS SOC 等五路獨立驗證,確認安時積分本身誤差極小,容量感覺偏低的真因是單體失衡鎖住充電空間,而非計數錯誤。
- 寄售系統新增台北捷運通路,利潤計算修正 — 從頭重寫捷運 parser 以支援兩份格式迥異的報表(銷售統計表 xlsx + 盤點表 xls);同步修正利潤計算為「實收 = 數量 × 定價 × 60%」,78 個 pytest 全過,庫存差異驗算為零後打包部署。
- sunnypilot 低速加速上限重新校準 — 0–18 km/h 加速上限比實際行車 log 高出近三倍,依用戶行車紀錄將斷點降至 0.72–0.78 m/s²,36 km/h 以上曲線完全不動,只修有問題的區段。
- sunnypilot 新增 lead_stop 獨立近停模組 — 針對停車距離偏遠問題,設計偵測、連續制動曲線、危險介入三層獨立邏輯,以雷達直接量測距離驅動煞車力道;誠實標注目前只有開環驗證,待實車確認。
- 資安弱掃修補:CSP 補上 frame-ancestors,移除頁尾明文信箱 — Clickjacking 弱點因正式機跑舊版設定,只在 Web.config 的 CSP 值尾端補上
; frame-ancestors 'self'一行,存檔自動觸發 App Pool 回收;三個 Razor view 頁尾的明文信箱與 mailto 連結直接移除,curl 驗證四個路徑均已生效。輸出兩份獨立 Word 回覆文件。 - 簽約預約系統平日車次上限上線 — 從 9/30 起對平日加入每日 18 台車上限,原 FullSaturdays() 改寫為通用 FullDates() 以 GROUP BY journeydate 一次撈出超限日期,議員個人額度邏輯完全未動。
- 神算2000 ERP 索引修復:281 個索引重建 — 確認 338 個 DBF 格式本身正確,根因是 ALLTRIM() 索引運算式在 VFP8/9 嚴格驗證下觸發 Error 2199;產生 FIXINDEX.PRG 重建 68 個受影響表的 281 個索引,並同步更新 CLEARBAR.DBF 中的 120 筆定義,阻斷日後維護操作把壞定義寫回去的路徑。
- 期貨策略實驗室新增單策略績效查看功能 — 新增策略清單與單策略績效兩支 API,前端新頁面支援 LIVE/PAPER 嚴格隔離、自訂日期區間、八項 KPI 與累計損益曲線,可匯出 CSV;採「只新增 endpoint 不動既有程式碼」原則降低盤中部署風險。
- YISEN 電子發票記帳士簡報重新定位完成 — 原功能說明書改為以記帳士業務動機出發,補上取數機制說明頁、修正憑證類型誤解(事務所用 XCA 組織憑證,非工商憑證),產出 21 頁可編輯 PPTX。
- 某客戶拍貼機統計重複計算 bug 修復 — 手動登記代幣佔位導致真實照片配不上去,實際 2 次被算成 4 次;修正配對腳本只以真實照片判斷已佔位,計數規則在有手動登記時以登記為準,逐日驗算歷史數字只有當天那筆被修正。
- 某客戶網購發票開立流程全線貫通 — 訂單在出貨或開立發票時才認列業績,修正 PosClient 連線問題與字軌重號 bug,新增「未列印」標記供 HQ 判斷待印票據,從開票到 HQ 同步全程實測 12 秒完成。
- 某客戶日報流程改為人工確認後發送 — 因網路不穩導致照片延遲、POS 未關班等狀況造成資料不完整,18:30 改為只產草稿,發送需人工按確認;草稿顯示資料完整性提示,已發送日報鎖定唯讀防止重發,送失敗停在草稿讓操作者重送。
- motoway.tw 多批次 GSC 索引提交完成 — 透過 FATE 自動化流程,完成 motoway.tw 多個頁面的 Google Search Console 索引要求提交,並完成 AdSense 擁有權驗證與複查提交。
本週技術心得 #
這週讓我反覆想到一個區別:「程式碼存在」和「流程走得通」是完全不同的兩件事。
對中小企業的 IT 導入來說,這個區別特別重要。外部整合(政府平台、金流、發票傳輸)的介面往往沒有完整文件,工程師第一版常常靠「業界慣例推測」寫出來——介面結構「大概是這樣」、selector「通常在這裡」、流程「應該是這個順序」。這樣的程式碼 lint 不報錯、單元測試也過,但真實流程從來沒走過。
這週 eMobile 密碼輪換的案例是最典型的:核心函式猜了一個「舊密碼欄必填」的前置條件,而目標平台根本沒有這個欄位。功能存在了兩個月,使用者不知道它壞的,因為沒有人實際觸發過密碼到期切換的場景。
防預這類問題的方法只有一個:在有真實目標的情況下寫第一行程式碼。早期取得實機帳號、操作文件、或至少一次真實的人工示範流程,成本遠低於之後的排查與修復。
另一個這週反覆出現的主題是「靜默失敗比明確報錯更難修」。AI 工具靜默降級模型、資料更新時間戳靜默消失、日報靜默吞掉送失敗——使用者看到的是「行為變了」而不是「出錯了」,排查路徑會長很多。設計系統時,遇到無法自動處理的狀況,讓流程停下來並告訴使用者,比讓它「看起來繼續了」更值得信任。
本文是 3Q 工程團隊的每週技術進度整理,由 Claude 協助編寫。內容聚焦技術類型與通用心得,不含客戶資訊。
