戰(zhàn):從零搭建天氣提醒機(jī)器人的AI輔助開發(fā)全流程)
上周末早上我手機(jī)彈出一條飛書消息“上海今日有雨記得帶傘”。這是我剛用Claude Code從零搭出來的天氣提醒機(jī)器人在準(zhǔn)點(diǎn)播報(bào)。算了一下從初始化項(xiàng)目到收到這條真實(shí)通知前后不到半天。更讓我意外的是這半天里真正由我手寫的代碼不超過二十行剩下的全是Claude Code根據(jù)我的需求描述補(bǔ)全的——整個(gè)開發(fā)工作流的節(jié)奏和我過去習(xí)慣的完全不一樣了。如果你也想體驗(yàn)AI輔助開發(fā)想看看Claude Code到底怎么融入日常開發(fā)又不愿意從 hello world 級(jí)別的demo開始那我建議你拿“天氣提醒機(jī)器人”這個(gè)項(xiàng)目來開刀。這篇文章會(huì)把我的完整過程拆開講環(huán)境配置、核心代碼、定時(shí)部署外加一次凌晨三點(diǎn)排錯(cuò)的完整復(fù)盤。1. 為什么偏偏選“天氣提醒機(jī)器人”當(dāng)?shù)谝粋€(gè)Claude Code項(xiàng)目很多人第一次接觸Claude Code會(huì)想讓它寫個(gè)商城、寫個(gè)后臺(tái)管理系統(tǒng)結(jié)果對(duì)話半小時(shí)還在搭框架最后不了了之。我當(dāng)時(shí)的判斷是天氣提醒機(jī)器人這種體量的項(xiàng)目才是驗(yàn)證AI開發(fā)工作流的最佳樣本。1.1 項(xiàng)目邊界清楚才知道AI在幫你干什么一個(gè)天氣提醒機(jī)器人拆開看無非三塊拉天氣數(shù)據(jù)調(diào)用天氣API拿到當(dāng)前或當(dāng)天的預(yù)報(bào)做判斷根據(jù)溫度、降水概率等字段決定要不要提醒、提醒什么內(nèi)容發(fā)通知通過飛書機(jī)器人、郵件或微信渠道推給用戶。每塊都有明確的輸入輸出。數(shù)據(jù)源的返回結(jié)構(gòu)是公開的通知發(fā)送成功與否一眼能看出來判斷邏輯就是幾條if語句。項(xiàng)目有沒有做完不需要?jiǎng)e人驗(yàn)收自己跑一次就知道。這種“邊界清楚”的項(xiàng)目特別適合用來觀察Claude Code的行為模式。你給它一個(gè)需求它會(huì)自己整理步驟、選庫、寫函數(shù)甚至把異常處理都補(bǔ)上。你能很清楚地區(qū)分哪些是它寫的、哪些需要你插手從而建立對(duì)AI輔助開發(fā)的直覺判斷。如果一上來就丟給它一個(gè)模糊的“幫我做個(gè)平臺(tái)”它也會(huì)迷茫你更無從判斷它的能力邊界。1.2 外部依賴豐富反而更適合練手這個(gè)項(xiàng)目好玩就好玩在它要接兩個(gè)外部系統(tǒng)——天氣API和消息推送。外部依賴多意味著你需要實(shí)實(shí)在在處理API鑒權(quán)、返回結(jié)構(gòu)解析、超時(shí)與重試這些才是真實(shí)開發(fā)里天天遇到的東西。我當(dāng)時(shí)選的是和風(fēng)天氣API的免費(fèi)開發(fā)者版。理由有兩個(gè)一是申請(qǐng)key的流程簡單注冊(cè)完就能用二是返回字段里有我需要的溫度、天氣現(xiàn)象、降水概率直接對(duì)應(yīng)機(jī)器人要做的“判斷”。如果你不在國內(nèi)或者有其他偏好換成OpenWeatherMap也一樣Claude Code對(duì)這類文檔化良好的API理解力都很強(qiáng)。通知通道我選了飛書自定義機(jī)器人Webhook。原因是免費(fèi)、配置快、調(diào)試直觀——往一個(gè)URL上POST一段JSON群里立刻有反應(yīng)。作為第一個(gè)版本反饋速度比什么技術(shù)棧都重要。這一階段我最想讓你記住的不是技術(shù)選型本身而是選型邏輯項(xiàng)目要能快速看見成果同時(shí)逼你去處理真實(shí)工程問題。天氣提醒機(jī)器人恰好同時(shí)滿足這兩點(diǎn)。模塊我用的方案選擇理由天氣數(shù)據(jù)和風(fēng)天氣API免費(fèi)額度夠用、JSON結(jié)構(gòu)簡單、文檔友好通知渠道飛書自定義機(jī)器人配置門檻低、消息反饋即時(shí)、支持簽名校驗(yàn)偏好設(shè)置配置文件環(huán)境變量不改代碼也能調(diào)整城市和閾值2. 先把環(huán)境立起來安裝、認(rèn)證與模型接入工欲善其事必先利其器。Claude Code雖然以“對(duì)話式開發(fā)”聞名它的本質(zhì)還是一個(gè)命令行工具。環(huán)境配好了后面所有流程才順。2.1 安裝命令和版本驗(yàn)證Linux與Windows安裝Claude Code最省心的一條路是npm。前提是你機(jī)器上有Node.js建議18以上版本。命令很簡單npm install -g anthropic-ai/claude-code裝完驗(yàn)證一下claude --version如果能打印出版本號(hào)說明安裝成功。如果你用的是Ubuntu這類Linux環(huán)境也可以走apt、官方腳本等方式但實(shí)測(cè)下來npm最不容易出幺蛾子因?yàn)楹罄m(xù)升級(jí)也是同一套命令。Windows上同樣用npm裝了之后建議在PowerShell里跑claude命令。如果你習(xí)慣VS Code后面我會(huì)單獨(dú)講插件的配置方式。Windows用戶最常遇到的兩個(gè)報(bào)錯(cuò)一個(gè)是claude: 無法識(shí)別說明npm全局安裝路徑?jīng)]加到PATH另一個(gè)是提示找不到Node先確認(rèn)node -v能正常輸出再重裝。2.2 認(rèn)證、密鑰與settings.json配置安裝只是第一步真正卡住不少人的是認(rèn)證。Claude Code運(yùn)行時(shí)需要一個(gè)可以訪問Anthropic API的身份憑證。兩個(gè)方式一是登錄Claude賬號(hào)走訂閱授權(quán)二是直接配置ANTHROPIC_API_KEY環(huán)境變量。我選擇的是API Key方式原因很實(shí)際API Key可以單獨(dú)配置、單獨(dú)撤銷而且方便后續(xù)切換到其他兼容模型時(shí)排查問題。設(shè)置方式export ANTHROPIC_API_KEY你的key想永久生效就寫進(jìn)~/.bashrc或~/.zshrcWindows上則在“系統(tǒng)環(huán)境變量”里加同名的用戶變量。配好之后重新打開終端讓變量生效。接下來是重點(diǎn)settings.json。Claude Code允許你通過配置文件控制它的行為默認(rèn)位置在~/.claude/settings.json也可以在項(xiàng)目目錄下放一份.claude/settings.json后者優(yōu)先。我項(xiàng)目里用到的配置大概長這樣{ permissions: { allow: [ Read, Glob, Bash(npm:*), Bash(python:*), Edit ], deny: [] }, model: claude-sonnet-4-0, includeCoAuthoredBy: true }這里的permissions是權(quán)限控制別看它不起眼實(shí)際用起來能幫你少踩一大堆坑。你可以限制Claude Code只能執(zhí)行哪些命令、只能編輯哪些文件避免它在你不知情的情況下把系統(tǒng)配置改得亂七八糟。我第一次沒設(shè)權(quán)限讓它自己跑一個(gè)項(xiàng)目結(jié)果它順手改了我的全局npm配置雖然能回滾但確實(shí)嚇一跳。所以從現(xiàn)在開始只要新建項(xiàng)目我都會(huì)把權(quán)限規(guī)則寫在配置文件里。2.3 VS Code插件與第三方模型接入如果你習(xí)慣在編輯器里干活Claude Code也有VS Code插件。裝好后側(cè)邊欄會(huì)多出一個(gè)會(huì)話面板可以直接選中代碼、右鍵發(fā)送給Claude Code。這一步的作用不是替代終端而是把AI生成的代碼和你的文件對(duì)比變得更直觀。插件也讀取同一套settings.json所以終端里配好的權(quán)限和模型編輯器里保持一致。至于第三方模型接入比如把Claude Code接到DeepSeek或其他兼容模型上本質(zhì)就是覆蓋API地址和模型名。環(huán)境變量可以這樣配export ANTHROPIC_BASE_URLhttps://compatible-endpoint.example.com export ANTHROPIC_MODELdeepseek-chat需要說清楚的是這種做法要求第三方提供Anthropic兼容接口或者你自己架一個(gè)適配層。我實(shí)際試過把輕量任務(wù)切到第三方模型跑日常寫寫腳本沒問題但涉及到復(fù)雜重構(gòu)、安全審查類任務(wù)我還是切回官方Claude系列。把工具鏈搭好比盲目追新更重要。這個(gè)項(xiàng)目里我全程用Claude Code官方模型第三方接入留到后續(xù)做成本優(yōu)化時(shí)再試。3. “提需求”式開發(fā)Claude Code生成核心代碼的完整過程環(huán)境配好之后真正的重頭戲才開始。我給你的建議是別把Claude Code當(dāng)成搜索引擎把它當(dāng)成一個(gè)隨時(shí)待命、而且愿意讀你整個(gè)倉庫的工程師。你的輸入是需求描述不是命令清單。3.1 我給的原始需求描述在項(xiàng)目目錄下執(zhí)行claude進(jìn)入交互界面我貼的第一段需求是這樣的幫我用Python寫一個(gè)天氣提醒機(jī)器人。需求 1. 從和風(fēng)天氣API拉取指定城市的今日天氣城市列表從config.json讀取 2. 如果今日有降水雨、雪、雨夾雪等通過飛書自定義機(jī)器人Webhook發(fā)送提醒 3. 如果沒有明顯降水但溫度低于5度也要發(fā)一條提醒注意保暖 4. 提醒文案需要包含城市、溫度、天氣現(xiàn)象、建議 5. API key、飛書webhook地址都從環(huán)境變量讀取不要硬編碼 6. 生成requirements.txt。這段描述沒有特別炫技但它把輸入、輸出、判斷規(guī)則、敏感信息處理方式都講清楚了。Claude Code拿到后沒有直接甩給我一段代碼而是先確認(rèn)了和風(fēng)天氣API的接口格式然后生成了項(xiàng)目文件結(jié)構(gòu)。我記得它第一版輸出了weather_bot.py、config.json、requirements.txt三個(gè)文件代碼可運(yùn)行結(jié)構(gòu)干凈。這給我一個(gè)很深的感受需求描述越接近“驗(yàn)收標(biāo)準(zhǔn)”AI生成的代碼越接近你想要的東西。別只說要做什么要連“怎么算成功”一起說清楚。3.2 拉取天氣數(shù)據(jù)的核心實(shí)現(xiàn)和風(fēng)天氣的免費(fèi)API返回的是嵌套JSONClaude Code生成的拉取函數(shù)比我自己手寫的還要謹(jǐn)慎。它的核心邏輯大致是這樣的import os import json import requests from typing import Dict, Any def fetch_weather(city: str, config: Dict[str, Any]) - Dict[str, Any]: 拉取指定城市的實(shí)時(shí)天氣返回精簡后的字段 api_key os.getenv(QW_WEATHER_API_KEY) if not api_key: raise RuntimeError(missing QW_WEATHER_API_KEY env) base_url https://devapi.qweather.com/v7/weather/now response requests.get( base_url, params{location: city, key: api_key}, timeout10 ) response.raise_for_status() payload response.json() if payload.get(code) ! 200: raise RuntimeError(fweather api error: {payload}) now payload[now] text now.get(text, 未知) temp float(now.get(feelsLike, 0)) precip now.get(precip, 0) return {city: city, text: text, temp: temp, precip: float(precip)}注意它主動(dòng)做了幾件事檢查環(huán)境變量是否存在、請(qǐng)求設(shè)置了timeout、對(duì)API返回碼做了校驗(yàn)還把寒冷字段、降水字段統(tǒng)一轉(zhuǎn)成float。這些細(xì)節(jié)如果你不主動(dòng)提容易成為AI生成代碼的盲區(qū)但Claude Code在第一版就帶上了。原因也不玄它訓(xùn)練時(shí)見過太多天氣API的接入代碼知道這類接口的常見坑。3.3 飛書機(jī)器人通知與郵件雙通道通知模塊是機(jī)器人能不能“被收到”的關(guān)鍵。飛書自定義機(jī)器人Webhook最基礎(chǔ)的使用方式就是POST一個(gè)JSON對(duì)象到指定URL。Claude Code生成的發(fā)送函數(shù)還額外加了一個(gè)可選簽名校驗(yàn)——Webhook地址里帶secret的時(shí)候需要在請(qǐng)求頭里帶上X-LC-Sign。import hashlib import base64 import hmac import time import requests def send_feishu(webhook: str, content: str, secret: str ) - None: 發(fā)送文本消息到飛書群 if secret: timestamp str(int(time.time())) string_to_sign f{timestamp}\n{secret} sign base64.b64encode( hmac.new( string_to_sign.encode(utf-8), digestmodhashlib.sha256 ).digest() ).decode(utf-8) sign_data {timestamp: timestamp, sign: sign} else: sign_data {} payload { msg_type: text, content: {text: content}, **sign_data, } response requests.post(webhook, jsonpayload, timeout8) response.raise_for_status()我在配置里同時(shí)留了郵件出口作為備用通道用smtplib包一層。理由很現(xiàn)實(shí)萬一飛書群需要維護(hù)或者webhook失聯(lián)機(jī)器人不能跟著啞掉。雙通道帶來的額外復(fù)雜度不大但關(guān)鍵時(shí)刻能救命。3.4 讓Claude Code自己給你講代碼我在這輪開發(fā)里學(xué)到的另一個(gè)技巧是讓Claude Code解釋它自己寫的代碼而不是急著往下加功能。只需要在對(duì)話里輸入 “逐行解釋一下fetch_weather這個(gè)函數(shù)尤其是異常處理部分”它會(huì)順著代碼路徑把每個(gè)分支的目的說清楚。這個(gè)習(xí)慣幫我提前發(fā)現(xiàn)了一個(gè)問題它用的是“體感溫度”而非“氣溫”來判斷冷暖而這其實(shí)正是我想要的冷熱提醒按體感走更準(zhǔn)確。如果沒問它這一句我大概率會(huì)把它當(dāng)bug改回去。不要默認(rèn)AI寫的都是你想要的但也別默認(rèn)AI寫的是錯(cuò)的。追問一下是最快的校驗(yàn)方式。4. 工作流重塑開發(fā)節(jié)奏從“寫代碼”變成“審代碼”代碼跑通之后我開始認(rèn)真思考Claude Code對(duì)工作流的影響。如果你長期用傳統(tǒng)IDE寫代碼第一次適應(yīng)“對(duì)話式開發(fā)”會(huì)有一種很奇妙的不安感鼠標(biāo)不再在文件里逐行游走而是在終端里不斷提出需求、審查diff、糾正方向。這種節(jié)奏調(diào)整帶來的效率變化是實(shí)打?qū)嵉摹?.1 第一次“整文件生成”帶來的震撼傳統(tǒng)開發(fā)流程里寫一個(gè)新功能通常要經(jīng)歷查文檔、建目錄、寫接口、寫模型、寫測(cè)試、調(diào)試聯(lián)調(diào)。每一步之間都有上下文切換成本。我在這個(gè)天氣項(xiàng)目里的流程卻是說清需求Claude Code直接生成整文件然后我一條條看diff。為了說明差異我列一個(gè)對(duì)照表環(huán)節(jié)傳統(tǒng)流程使用Claude Code后理解需求找人確認(rèn)、翻文檔AI會(huì)基于倉庫上下文自述理解編寫代碼逐行手寫整文件生成我審查diff調(diào)試打斷點(diǎn)、翻日志把報(bào)錯(cuò)信息直接扔給Claude Code補(bǔ)充文檔最后補(bǔ)或者不補(bǔ)讓AI順手寫README、注釋變更維護(hù)手動(dòng)改多處調(diào)用給出變更點(diǎn)清單AI分批改最舒服的是改需求。比如某天我想把“降水提醒”改成“降水或風(fēng)力大于5級(jí)都提醒”命令是讓Claude Code先搜索現(xiàn)有判斷邏輯然后給出影響范圍再動(dòng)手改。它會(huì)在改動(dòng)前列出涉及的文件和測(cè)試點(diǎn)相當(dāng)于幫你過了一遍靜態(tài)檢查。這比我過去用IDE全局搜索來得高效得多。4.2 1M上下文與Skills怎么用才不浪費(fèi)Claude Code有個(gè)很夸張的上下文能力實(shí)測(cè)塞進(jìn)一個(gè)小型項(xiàng)目倉庫完全沒有問題。天氣機(jī)器人這種體量把全部代碼、配置文件、README一次性放進(jìn)上下文中它也能記住來龍去脈。這意味著你后面發(fā)出任何一個(gè)指令它都帶著整個(gè)項(xiàng)目的內(nèi)容在做判斷省去了“你把那個(gè)文件里的邏輯再講一遍”的尷尬。但上下文大不等于可以放手不管。我建議你善用“Skills”機(jī)制——它允許你把常用的指令打包成項(xiàng)目級(jí)命令。比如我給天氣機(jī)器人定義了一個(gè)skill叫“天氣需求變更”內(nèi)容是修改天氣參數(shù)時(shí)先定位配置文件、再修改weather_bot.py、最后更新README中的參數(shù)說明。這樣一來后續(xù)我只需要輸入那條skill名Claude Code就會(huì)自動(dòng)執(zhí)行“定位、修改、同步文檔”這個(gè)三步流程。4.3 思考等級(jí)與Workflows復(fù)雜任務(wù)的分步推進(jìn)另外要提一下Claude Code的思考等級(jí)參數(shù)。日常小改動(dòng)比如“把日志加上中文前綴”我用默認(rèn)等級(jí)就能搞定但遇到“把通知模塊從飛書擴(kuò)展到企業(yè)微信同時(shí)保證不重復(fù)通知”這種涉及多處代碼的任務(wù)我會(huì)用claude --thinking xhigh這樣的參數(shù)提高思考等級(jí)。高等級(jí)下它會(huì)更謹(jǐn)慎地拆解任務(wù)、列出風(fēng)險(xiǎn)點(diǎn)代價(jià)是響應(yīng)變慢但值。這種“按任務(wù)復(fù)雜度調(diào)整思考投入”的做法其實(shí)可以外化成workflows。我的習(xí)慣是把一套完成某個(gè)業(yè)務(wù)目標(biāo)的對(duì)話路徑固化下來讓Claude Code分步執(zhí)行。天氣機(jī)器人從開發(fā)到上線我就是按“搭建骨架 → 實(shí)現(xiàn)API接入 → 實(shí)現(xiàn)通知 → 配置定時(shí)調(diào)度 → 寫出部署說明”這五步推進(jìn)的。每一步之間我會(huì)檢查diff確認(rèn)沒有跑偏再進(jìn)入下一步。整體感受像在做一場(chǎng)微妙的結(jié)對(duì)編程它動(dòng)手我掌舵。5. 無人值守不是把程序丟給cron那么簡單機(jī)器人代碼寫完了本地跑一遍也正常。但“能跑”和“無人值守地跑”是兩個(gè)完全不同的概念。天氣提醒機(jī)器人要讓人覺得可靠就得定時(shí)觸發(fā)、失敗重試、還要防止重復(fù)通知。5.1 cron與Windows任務(wù)計(jì)劃程序的實(shí)際差別我先在Linux服務(wù)器上部署。由于這是Python腳本定時(shí)觸發(fā)我用的是crontab。下面是我配置的定時(shí)任務(wù)0 7 * * * cd /opt/weather-bot /usr/bin/python3 weather_bot.py logs/weather.log 21這行的意思是每天早晨7點(diǎn)整先進(jìn)入項(xiàng)目目錄再執(zhí)行天氣腳本標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出都追加到日志文件。注意我寫的是絕對(duì)路徑/usr/bin/python3而不是python3因?yàn)閏ron執(zhí)行環(huán)境不受用戶環(huán)境變量影響直接寫命令名容易觸發(fā)“找不到命令”的坑。Windows上則是任務(wù)計(jì)劃程序。創(chuàng)建基本任務(wù)觸發(fā)器選“每天”時(shí)間設(shè)成7點(diǎn)操作指向python.exe參數(shù)填腳本完整路徑起始目錄填項(xiàng)目根目錄。這里最容易踩的坑有兩個(gè)一是python.exe如果你沒有勾選“使用絕對(duì)路徑”系統(tǒng)可能找不到二是任務(wù)計(jì)劃程序有自己的權(quán)限上下文如果腳本需要讀環(huán)境變量建議在任務(wù)設(shè)置里手動(dòng)填一遍或者讓腳本內(nèi)部加載一個(gè).env文件。5.2 日志、重試與防重復(fù)通知定時(shí)任務(wù)跑起來后日志就是你的第一道防線。我給腳本入口加了一個(gè)簡單的日志模塊每次執(zhí)行都記錄執(zhí)行時(shí)間、拉取到的城市列表、判斷結(jié)果、通知是否發(fā)送成功。重試邏輯也必須有。天氣API偶爾會(huì)超時(shí)飛書Webhook也可能短暫不可用。我給請(qǐng)求層統(tǒng)一加上重試策略最多重試2次間隔3秒且只在網(wǎng)絡(luò)異常時(shí)重試API返回業(yè)務(wù)錯(cuò)誤碼則不重試。最后是防重復(fù)通知。定時(shí)任務(wù)每天只跑一次理論上不會(huì)重復(fù)但實(shí)際開發(fā)中你會(huì)反復(fù)手動(dòng)跑腳本做測(cè)試。如果不加任何保護(hù)每手動(dòng)跑一次群里的通知就多發(fā)一遍。我的做法是加一個(gè)“通知去重文件”記錄當(dāng)天已經(jīng)通知過的城市列表如果腳本在同一天再次運(yùn)行且某城市已經(jīng)通知過就直接跳過發(fā)送。from pathlib import Path sent_log Path(/tmp/weather_bot_sent.json) def already_notified(city: str, today: str) - bool: if not sent_log.exists(): return False data json.loads(sent_log.read_text(encodingutf-8)) return data.get(date) today and city in data.get(cities, []) def mark_notified(city: str, today: str) - None: if sent_log.exists(): data json.loads(sent_log.read_text(encodingutf-8)) else: data {date: today, cities: []} data[date] today if city not in data[cities]: data[cities].append(city) sent_log.write_text(json.dumps(data), encodingutf-8)這段邏輯不復(fù)雜但它把“每天最多通知一次”這個(gè)業(yè)務(wù)規(guī)則真正落地了。沒有這層防護(hù)的機(jī)器人遲早會(huì)在某次重復(fù)調(diào)度中讓你在群里社死。6. 一次“凌晨三點(diǎn)連發(fā)三條通知”的排查全記錄如果你覺得前面都是順利時(shí)刻那接下來這個(gè)坑會(huì)讓你對(duì)自動(dòng)化工具保持清醒。在我把機(jī)器人部署到服務(wù)器后的第三天群里在凌晨三點(diǎn)瘋狂彈出三條天氣提醒。所有人都很懵因?yàn)槎〞r(shí)任務(wù)明明設(shè)的是早晨7點(diǎn)。6.1 現(xiàn)象與第一反應(yīng)凌晨3:00飛書群收到三條幾乎相同的消息城市、溫度、提醒文案一模一樣。第一次收到還能解釋為測(cè)試連續(xù)三次就一定是邏輯問題了。我的第一反應(yīng)不是去改代碼而是先冷靜記錄現(xiàn)象三條消息間隔多久、內(nèi)容是否完全一致、有沒有從日志里看到重復(fù)執(zhí)行痕跡。這里給你一個(gè)排查原則先把現(xiàn)象收集完整再做任何改動(dòng)。很多隱蔽bug之所以難定位就是因?yàn)榕挪檎咭簧蟻砭透牧舜a把現(xiàn)場(chǎng)破壞了。6.2 鏈路排查crontab、日志、代碼逐個(gè)過第一步檢查crontab。執(zhí)行crontab -l后發(fā)現(xiàn)系統(tǒng)里居然有兩條任務(wù)一條是原來的0 7 * * *另一條是我當(dāng)天下午測(cè)試新版配置時(shí)留下的一條0 3 * * *而且兩條都把輸出寫進(jìn)同一個(gè)日志文件。為什么會(huì)有第二條回想起來當(dāng)時(shí)為了讓某個(gè)變更盡快驗(yàn)證我臨時(shí)改了計(jì)劃任務(wù)驗(yàn)證后又沒刪干凈。這是典型的“臨時(shí)操作沒有收尾”事故。第二步看日志。日志里凌晨3點(diǎn)確實(shí)有三次執(zhí)行記錄。一次來自那條殘留的cron任務(wù)另外兩次則是腳本內(nèi)部的重試機(jī)制在作怪。因?yàn)榱璩烤W(wǎng)絡(luò)狀態(tài)不穩(wěn)前兩次發(fā)送飛書消息時(shí)都碰到了超時(shí)觸發(fā)了我之前配置的2次重試。于是一次調(diào)度加上兩次重試三條完全一樣的消息。第三步定位到推送函數(shù)。問題并不在推送代碼本身而在于“重試”和“調(diào)度”疊加時(shí)缺少了我在第5章提到的防重復(fù)通知保護(hù)。日志文件路徑我知道但去重文件的工作目錄在cron里變成了/導(dǎo)致sent_log.exists()一直判斷為False去重機(jī)制完全失效。到這里結(jié)論清晰了殘留的cron任務(wù) 網(wǎng)絡(luò)超時(shí)重試 去重狀態(tài)文件路徑失效三個(gè)問題疊加才導(dǎo)致三連發(fā)。6.3 修復(fù)、去重與自動(dòng)化配置的教訓(xùn)修復(fù)分三步。第一立刻刪除殘留的cron任務(wù)讓調(diào)度回到唯一正確的時(shí)間點(diǎn)第二修改腳本讓日志和狀態(tài)文件都使用絕對(duì)路徑不再依賴“當(dāng)前工作目錄”第三把重試機(jī)制從“發(fā)送函數(shù)內(nèi)部”改成“統(tǒng)一在調(diào)度入口控制”即只有調(diào)度任務(wù)觸發(fā)的執(zhí)行才允許重試手動(dòng)執(zhí)行則每次只發(fā)一次方便測(cè)試。import os BASE_DIR os.path.dirname(os.path.abspath(__file__)) sent_log Path(BASE_DIR) / data / sent.json這段代碼解決的問題是讓我不再依賴cron設(shè)置的起始目錄。腳本無論被誰調(diào)用都能穩(wěn)定找到自己的狀態(tài)文件。這輪排錯(cuò)給我最大的教訓(xùn)不是不要配錯(cuò)cron而是要尊重“操作留痕”。所有對(duì)生產(chǎn)環(huán)境的臨時(shí)改動(dòng)都應(yīng)該通過CLI工具記錄如果手滑留下了多余配置也要立即清理。Claude Code能幫你生成命令、補(bǔ)全配置但它不會(huì)替你判斷“這條命令是否應(yīng)該長期存在”。自動(dòng)化工具越強(qiáng)人的審查責(zé)任反而越重。排完這個(gè)坑之后我又把同樣的防護(hù)邏輯補(bǔ)到了郵件通知通道?,F(xiàn)在這臺(tái)天氣機(jī)器人在服務(wù)器上安安穩(wěn)穩(wěn)跑了好幾周每天早上7點(diǎn)準(zhǔn)時(shí)出現(xiàn)遇到已經(jīng)通知過的城市絕不多說一句廢話。我自己實(shí)操下來最大的感受是讓AI寫代碼、做配置可以很爽但讓AI做主流程的設(shè)計(jì)決策、承擔(dān)運(yùn)維結(jié)果的最后一道審查這件事現(xiàn)階段必須留在人手里。一個(gè)可靠的開發(fā)工作流是讓Claude Code去做它擅長的快速產(chǎn)出而你專注做它暫時(shí)做不到的邊界判斷和復(fù)盤。