首頁·報錯訊息

報錯訊息逐條解釋

104 條報錯訊息,逐條講清含義、起因和處理辦法。

修法都有代價。git reset --hard 會丟掉沒提交的工作,強制推送可能毀掉同事的提交,docker system prune 會刪掉未命名的卷。凡是這樣的條目,裡面都寫明了。

Git29

git 的錯誤幾乎都是一種拒絕,意思是「在目前狀態下做那件事會丟掉東西」;fatal: 表示它什麼都沒改就停了,而真正的解決辦法往往在第一行下面的 hint: 行裡。

fatal: refusing to merge unrelated histories你要合併的兩邊沒有任何共同祖先,在 git 看來它們是兩段互不相干的歷史。這通常發生在你本機用 git init 開始、提交了幾次,然後把一個已經有提交的遠端加為 origin 再 pull 的時候。加上 --allow-unrelated-histories 確實能合併,但它把兩段無關歷史硬焊在一個提交上,以後很難拆開;如果本機只有幾個提交,重新 clone 遠端再把檔案搬進去更乾淨。Your branch and 'origin/main' have diverged你的分支和 origin/main 各自都有對方沒有的提交,也就是歷史分成了兩條。這發生在你本機提交的同時別人往遠端 push 了,或者你用 amend、rebase 改寫了已經推上去的提交。git pull --rebase 把你的提交重新疊到對方之上、讓歷史保持一條線,但會給你的提交換上新的雜湊值,所以如果這些提交已經和別人共用過,同一份工作就會留下兩遍;這種時候用 --no-rebase 留一個合併提交更穩。fatal: Need to specify how to reconcile divergent branches.從 git 2.34 起,分支分岔時 pull 不再替你猜是要合併還是 rebase,它在要一個設定,而不是在報告損壞。當兩條分支已經分岔而你從未設過 pull.rebase 時就會看到它。pull.ff only 最穩妥,因為它只在能快進時才拉取,否則什麼都不建就停下;pull.rebase true 每次都會改寫你本機提交的雜湊值,而 false 會留下一個合併提交。CONFLICT (content): Merge conflict in src/app.tsx兩邊把同樣的行改成了不同的樣子,git 無法判斷哪個對,於是把兩個版本都寫進檔案裡,夾在 <<<<<<<、======= 和 >>>>>>> 之間,然後停下。這發生在你和別人改動了同一段,或者 rebase 把你的提交挪過了對這幾行的修改。打開檔案改成該有的樣子,刪掉這些標記,然後 git add 再 commit 就完了。git merge --abort 會精確回到合併之前的狀態,對已提交的東西沒有風險,但你手動解決過的部分會一起丟掉。Please commit your changes or stash them before you merge.你有未提交變更的檔案,正好也是即將拉進來的提交要改的檔案,git 選擇停下而不是覆蓋它們。它出現在工作區不乾淨時執行 pull 或 merge。git stash push -u 把這些變更先挪到一邊讓合併通過,-u 會連未追蹤的檔案一起收;但 git stash pop 取回時仍可能衝突,而且 stash 不像分支那樣看得見,很容易忘掉丟掉,所以隨手做一個暫時提交是更穩的習慣。error: Your local changes to the following files would be overwritten by checkout:你想切到另一個分支,但那邊內容不同的檔案裡還留著你沒提交的變更。它出現在活幹了一半就 switch 或 checkout 的時候。git stash push -u 或者隨手一個暫時提交能讓你過去,之後還能回來。搜尋裡常見的 git checkout --force 和 git switch --discard-changes 確實能切過去,但會永久刪掉那些未提交的變更——連 reflog 都不會留下記錄,沒有任何找回的辦法。You are currently rebasing branch 'feature' on '8a3f21c'.一次 rebase 開始後停在了中途,HEAD 停在一個暫時狀態上,而不是你的分支上。你會在 rebase 期間發生衝突之後,或者 git rebase -i 在 edit、break 處停下之後看到這一行——你沒把它做完就去幹別的了。解決衝突後 git add,再用 git rebase --continue 繼續;如果打算丟掉那個提交就用 --skip。git rebase --abort 會精確回到 rebase 開始之前的位置,已提交的東西一點不丟,但你到目前為止手動解決的衝突會一起消失。You are in 'detached HEAD' state.HEAD 直接指向某個提交而不是分支名,所以你在這裡做的任何提交都不會掛上名字。切到某個提交雜湊值或標籤、在子模組裡幹活、或者 CI 簽出了某個特定提交,都會進入這個狀態。想留住這裡做的活,git switch -c 名字 就地建一個分支,什麼都不會丟。如果你直接 git switch main 離開,你剛做的提交就沒有任何東西指向它:reflog 還能找一段時間,等垃圾回收跑過就沒了。Warning: you are leaving 1 commit behind, not connected to any of your branches:它在警告:你在 detached HEAD 狀態下做了提交,現在要離開,而沒有任何分支指向那些提交。這發生在你切到某個提交雜湊值、幹活、提交,然後又回到分支的時候。直接用訊息裡印出來的雜湊值:git branch rescue 8a3f21c 就把它們釘在一個名字上,這條命令別的什麼都不改,只加一個名字。如果你已經離開了,git reflog 裡還列著它們,但沒有任何東西指向的提交會在垃圾回收跑過後被清掉——預設大約三十天。error: failed to push some refs to 'https://github.com/user/repo.git'伺服器拒絕了這次 push,這一行只是結果摘要——真正的原因寫在它上面那行 ! [rejected] 裡。多數情況是遠端有你沒有的提交,但受保護分支、伺服器端掛鉤、檔案大小限制也會給出同樣的摘要。先把上面的原因讀掉;普通情形下 git pull --rebase && git push 就結束了。最危險的動作是不讀原因就加 --force,那可能把同事在伺服器上的提交抹掉。! [rejected] main -> main (fetch first)遠端分支上有你的複製從沒見過的提交,所以現在 push 就不是快進了。這發生在同事先 push 了,或者你上次 fetch 已經很久之前。git pull --rebase origin main 把那些提交拉進來、再把你的疊上去,然後 push 就能過。這裡絕對不要用 --force:你會覆蓋的提交是別人的,而且根本不在你的儲存庫裡,本機任何東西都找不回來。! [rejected] main -> main (non-fast-forward)你的分支不是遠端分支的後代,照原樣推上去會讓伺服器上已有的提交從歷史裡掉出去。這發生在你用 rebase、amend、reset 改寫了已經推上去的提交。如果改寫是有意的、而且這條分支只有你在用,就用 git push --force-with-lease:只要你上次 fetch 之後遠端動過,它就會拒絕,所以和光禿禿的 --force 不同,它不會悄悄抹掉這期間同事推上來的東西。如果改寫不是有意的,答案是 pull --rebase,而不是強推。Updates were rejected because the remote contains work that you do not have locally.這是 push 被拒的原因說明:遠端有你的複製沒有的工作,現在推上去會把它刪掉。它出現在多人共用一條分支時,或者你在網頁上改檔案提交後一直沒拉下來。先 git pull --rebase origin main,再 push,就順利通過了。不理這條提示而加 --force,正好做了提示要防的事:別人的提交從伺服器上消失,而如果他 push 之後沒人 fetch 過,這些提交就哪裡都不剩了。error: src refspec main does not match any你讓 git 推的那個名字,在本機既不是分支也不是標籤,不存在的東西沒法送出去。這發生在剛建的儲存庫還沒有任何提交、main 這個名字還沒誕生的時候;或者本機分支叫 master 而你打的是 main;又或者只是打錯字。git push -u origin HEAD 會把你實際站著的那條分支按它真正的名字推上去,一步就繞過名字對不上的情況。如果一個提交都沒有,先做一個——空的儲存庫沒有可送的東西。fatal: The current branch feature has no upstream branch.這條分支沒有登記對應的遠端分支,所以不帶參數的 git push 或 git pull 不知道該去哪裡。它出現在你用 git switch -c 新建分支後從未推送過的時候。git push --set-upstream origin feature 會在伺服器上建出這條分支,並把對應關係登記一次,此後只打 git push 就夠了。這條命令只寫一行本機設定和伺服器上一條新分支,什麼都不刪,重複執行也安全。error: cannot lock ref 'refs/remotes/origin/main': is at 8a3f21c but expected 1c2d3e4git 正要往一個遠端追蹤參照寫入新值時,發現裡面的值和它剛讀到的不一樣,於是拒絕覆蓋。常見於編輯器在背景 fetch 而你同時也在 fetch,或者有人做了 force push、你的遠端追蹤參照錯位了。多數情況下再 fetch 一次就過去了。git remote prune origin 會清掉上游已經不存在的分支所對應的遠端追蹤參照;它刪的只是你儲存庫內部的副本,絕不碰伺服器上的分支。fatal: not a git repository (or any of the parent directories): .git目前目錄以及它上面的任何目錄裡都沒有 .git,git 找不到儲存庫。這發生在你站在儲存庫上一層或下一層、複製落到了別的資料夾、或者你從來沒跑過 git init 的時候。先用 pwd 確認自己在哪,只有真的想在這裡開一個儲存庫時才用 git init。在已有儲存庫的子資料夾裡跑 git init 會在裡面生出第二個嵌套儲存庫,悄悄把外面那個遮住,從那以後這個資料夾裡的檔案再也不會進入外層的提交。fatal: remote origin already exists.在這個儲存庫裡 origin 這個名字已經被佔用了,不能再建一個同名的。複製下來的儲存庫一開始就有 origin,而這條訊息出現在你在其中照著「新建儲存庫」的教學執行 git remote add origin 的時候。先用 git remote -v 看看 origin 指向哪裡;如果你想換的是位址,git remote set-url origin 只改指向。這個設定是 .git/config 裡的一行,隨時可以改回來,對你的提交沒有任何影響。error: pathspec 'featue' did not match any file(s) known to gitgit 沒有找到叫這個名字的檔案、分支或標籤——引號裡的那串字,就是它實際去找的名字。這可能是打錯字,或者那條分支只在伺服器上而你還沒 fetch,又或者檔案不在你以為的那個目錄裡。是分支就先 git fetch 再用 git branch -a 把名字對著看;是檔案就用 git status --short 看真實路徑。要記得 git 命令裡的路徑是相對你目前所在目錄解析的,不是相對儲存庫根,光這一點就能解釋其中很多情況。git@github.com: Permission denied (publickey).ssh 已經連到了伺服器,但沒能拿出任何伺服器願意接受的金鑰——這是認證問題,不是網路問題。它出現在你還沒產生金鑰、產生了卻沒加進 ssh-agent、或者公開金鑰沒有登記到該服務的帳號上的時候。ssh-add ~/.ssh/id_ed25519 會在本次工作階段裡把金鑰裝進 agent,而 ssh -T git@github.com 會告訴你試了哪把金鑰、你被認成了誰。兩條命令都只讀,不會有任何損失。remote: Support for password authentication was removed on August 13, 2021.GitHub 在 2021 年停止接受透過 https 的帳號密碼——不是你的密碼錯了,而是這種方式本身不再被接受。它出現在你儲存的憑證是舊密碼,或者你在提示處直接輸入了密碼的時候。想繼續用 https,就在密碼欄裡填 personal access token;否則用 git remote set-url origin 換成 ssh 位址。改遠端位址只是你自己複製裡的一行設定,不動任何提交,隨時可以改回來。fatal: Authentication failed for 'https://github.com/user/repo.git/'送出去的憑證被拒了——它是錯的、過期的,或者是一個沒有這個儲存庫所需權限範圍的 token。常見的坑是過期 token 還留在憑證助手的快取裡:它連問都不問就被送出去,所以每次都是同一行。修復裡那條 git credential reject 只刪掉那一條儲存的記錄,讓系統重新向你詢問,你就能貼上新的 token。它只刪已儲存的憑證,不動儲存庫的任何資料。fatal: Unable to create '/repo/.git/index.lock': File exists.git 寫索引時會建 .git/index.lock 當鎖,所以這個檔案已經存在,意味著另一個 git 正在跑,或者有一個死掉時把它留下了。它常被背景跑 git 的編輯器或 IDE 留下,也可能是你用 Ctrl+C 打斷或殺掉的命令留下的。先確認沒有 git 在跑,再用 rm -f .git/index.lock 刪掉。在真有 git 行程幹活時刪它可能弄壞索引,所以務必先確認;萬一索引亂了,git reset 能從 HEAD 重建它,不加 --hard 就不會動你的檔案。nothing to commit, working tree clean這不是錯誤:git 在告訴你,和上一個提交相比沒有任何差異,所以它什麼也沒做。你會在改的檔案被 .gitignore 命中、你編輯的地方其實是另一個複製或 worktree、或者你已經提交過卻忘了的時候看到這一行。git check-ignore -v <路徑> 會印出遮住這個檔案的那條 .gitignore 規則本身,而 git log -1 能確認這次變更是不是已經進去了。兩條命令都只讀。error: unable to unlink old 'dist/main.js': Permission deniedcheckout 想把一個檔案換成新版本,而作業系統不讓刪掉舊的。這發生在別的程式把這個檔案開著(Windows 上尤其常見),或者你對這個目錄沒有寫入權限的時候。把佔著它的東西關掉——開發伺服器、編輯器、防毒軟體——再跑一遍同樣的命令;在 Unix 上則修正目錄權限。重複這條命令是安全的,但要注意 git 是停在中途的,所以在成功之前工作區一直處於只更新了一半的狀態。fatal: bad object 8a3f21c你給的名字解析不到任何 git 讀得懂的東西:要麼這個物件不在這個儲存庫裡,要麼在裡面但已經損壞。這發生在雜湊值是從別的複製或淺層複製裡抄來的、雜湊值被截斷得太短、或者磁碟出問題後物件真的壞了的時候。git fsck --full 只讀地報告缺失和損壞的物件,而別人給的雜湊值要先 git fetch,那個物件才會到你這裡。如果 fsck 真報出損壞,重新 clone 比修補更快也更可靠——只要先把未提交的檔案抄到安全的地方。warning: LF will be replaced by CRLF in package.json.這是提示,不是錯誤:core.autocrlf 是開著的,所以 git 在提交裡存 LF,卻會往你的工作副本裡寫 CRLF。在 Windows 上按預設值安裝 git 會把 autocrlf 設成 true,所以那台機器上每次 add 都會看到它。git config core.autocrlf input 會在提交裡存 LF、取出時不再轉換;更好的辦法是在儲存庫裡放一個 .gitattributes,寫上 * text=auto eol=lf,讓每個複製行為一致。改這個設定可能讓所有檔案看起來都被改過一次;那只是一次重新取出,不是丟了工作。The file will have its original line endings in your working directory這是上面那條 CRLF 提示的後半句:它說磁碟上的檔案保留它現有的行尾,只有存進提交的那份副本被正規化。它來自同一個 autocrlf 設定,本身沒有任何東西壞掉。但如果 diff 裡整個檔案都顯示被改過,那就是團隊裡行尾不一致的信號:在儲存庫裡放一個 .gitattributes 把規則釘住,再跑一次 git add --renormalize .。這條命令只改寫行尾的儲存方式,不改檔案內容。husky - pre-commit hook exited with code 1 (error)你的提交根本沒有產生:某個掛鉤——lint、測試、格式化——以非零碼結束,git 中止了提交。真正的原因不在這一行,而在它上面掛鉤自己的輸出裡,通常是一條 lint 規則或型別錯誤。把掛鉤指出的問題改掉再提交,是唯一真正的解法。git commit --no-verify 會跳過所有提交掛鉤並確實產生提交,但它不是通過了檢查,只是把失敗推給了 CI,未格式化的程式碼會原樣送到同事那裡。

npm20

npm 的原因寫在第一行 code XXXX 裡,而不是最後那六行 npm ERR!;如果失敗來自相依樹或原生編譯而不是你自己的程式碼,刪掉 node_modules 重新安裝就能解決一半。

npm ERR! ERESOLVE unable to resolve dependency tree從 npm 7 起 peerDependencies 的版本範圍會被強制執行,這句話是說 npm 找不到一組能同時滿足所有範圍的版本。常見於你要裝的套件的 peer 範圍排除了你手上已有的 react 或 typescript 版本,尤其是剛做完一次大版本升級之後。讀輸出裡的 Found: 和 Could not resolve: 兩行,看清誰要求什麼,然後把兩者之一挪到相容的版本——這才是真正的修法。npm install --legacy-peer-deps 會完全無視 peer 範圍直接裝上,能讓你繼續往下走,但留下的是一棵各個套件從未認可的相依樹,版本不吻合帶來的執行時錯誤得你自己去查。npm ERR! Conflicting peer dependency: react@18.3.1這一行從 ERESOLVE 的報告裡挑出了真正衝突的那一對:這個套件要求某個 peer 的那個版本,而你的相依樹裡裝的是另一個。通常意味著主要函式庫做了大版本跳躍,而用它的外掛還沒跟上。按名字問一句 npm ls react,就會以樹的形式印出誰要求了哪個版本,該動這兩者中的哪一個當場就清楚了。把外掛升上去才是真正的修法;--force 照樣把不吻合的樹寫下去,把問題藏到執行時才爆。npm WARN react-dom@17.0.2 requires a peer of react@17.0.2 but none is installed.npm 6 只對 peer 相依發警告,從來不替你裝:套件是裝上了,但它說自己需要的那個伙伴沒有。它出現在仍停留在 npm 6 的專案裡,或者你在用那個年代留下的舊 lock 檔。按警告裡印出的範圍自己裝上那個 peer 就完事了。現在還沒有任何東西失敗,因為這只是警告,但它稍後會以 Cannot find module 的形式回來,或者變成同一個函式庫裝了兩份、報出莫名其妙的錯。npm WARN deprecated request@2.88.2: request has been deprecated這個套件的作者把那個版本標成了不再推薦:它裝上了,也照樣能跑。它通常不是你自己 package.json 裡的東西,而是你在用的某個套件順帶拖進來的間接相依。按名字問一句 npm ls request,就能看到是你哪個直接相依把它帶進來的,要動的地方是那個直接相依,而不是這個套件本身。今天不用做任何事——deprecated 不是安全漏洞,安全漏洞是 npm audit 告訴你的。npm ERR! npm ci can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync.npm ci 只按 lock 檔裡記錄的東西裝,而它連開始都沒開始,因為 package.json 要的東西 lock 檔裡沒有。這發生在有人手改了 package.json、或在裡面解了衝突卻沒跑 install,又或者只提交了 package.json 而沒把 lock 檔一起提交。在本機跑一次 npm install 讓 lock 檔對上,然後把 lock 檔提交——這就是全部的修法。為了繞過它而刪掉 package-lock.json,會把所有相依重新解析到更新的版本,悄悄改變你建置裡的內容。npm ERR! The `npm ci` command can only install with an existing package-lock.json or npm-shrinkwrap.json沒有 lock 檔,npm ci 根本不會執行,因為沒有任何東西告訴它該裝哪些版本。這發生在 package-lock.json 被 .gitignore 擋住、從未被提交過、或者你目前所在的目錄不是專案根的時候。npm install --package-lock-only 會只產生 lock 檔而不裝任何東西,把它提交上去,CI 就能跑了。千萬不要把 package-lock.json 放進 .gitignore:CI 兩次建置出同樣東西的保證,全靠這一個檔案。npm ERR! Invalid Version:package.json 裡的 version 不是合法的 semver,所以 npm 根本沒法解析這個套件。它出現在手改成 1.0、v1.0.0 或空字串這類值的時候,尤其是這個檔案的衝突沒解好之後。npm pkg set version=1.0.0 會寫回一個規範的三段版本號。這條命令只改 package.json,既不動 node_modules 也不動 lock 檔,所以改完之後所有讀這個檔案的命令立刻恢復正常。npm ERR! 404 Not Found - GET https://registry.npmjs.org/@acme/ui - Not found登錄檔裡沒有這個名字的套件——404 是關於名字的回答,不是關於你的網路或憑證的。它出現在名字打錯、套件被取消發布、或者你沒登入的私有 scope 上。對沒有權限的人來說,私有套件看起來和不存在的套件完全一樣,所以這三種情況都擠在這一行裡。npm view @acme/ui version 先確認這個名字是否公開存在;如果是私有 scope,檢查 .npmrc 裡有沒有那個 scope 的登錄檔和 token。兩項檢查都只讀。npm ERR! request to https://registry.npmjs.org/express failed, reason: unable to verify the first certificateTLS 連線失敗了:伺服器出示的憑證鏈最終指向 node 不信任的機構。絕大多數情況是公司網路的代理把流量拆開、用自己的憑證重新簽章;偶爾是伺服器沒有把中繼憑證一起發過來。用 npm config set cafile 把公司的根憑證告訴 npm,這才是正確的修法。strict-ssl false 也能讓這行消失,但那是把憑證驗證整個關掉,路徑上的任何人都能給你送來被改過的套件——不要用。npm ERR! code EINTEGRITYnpm 下載下來的壓縮檔雜湊值和 lock 檔裡記錄的不一致,於是 npm 判定收到的東西不可信並停下。原因是快取項目壞了、代理動過下載內容,或者更少見地,lock 檔裡的 integrity 值被手改過或合併出了錯。npm cache clean --force 會清空本機快取,下次安裝就重新下載;它只刪快取副本,重複執行也安全。如果只在某一個套件上反覆出現,那是記錄的 integrity 本身就錯了,需要用 npm install 重新解析那一項。npm ERR! Error: EACCES: permission denied, access '/usr/local/lib/node_modules'npm 想往一個不屬於你這個使用者的系統目錄裡寫——套件本身沒問題,你只是沒有在那兒寫的權限。這幾乎總是對著系統套件管理器裝在 /usr/local 之類位置的 node 執行 npm install -g。npm config set prefix ~/.npm-global 把全域安裝挪到你的家目錄,把它下面的 bin 加進 PATH,這個錯就不會再來。sudo npm install -g 確實能裝上,但會在快取和 node_modules 裡留下 root 所有的檔案,日後普通安裝照樣報同一個 EACCES;用 nvm 這類版本管理器可以讓整類問題消失。npm ERR! enoent ENOENT: no such file or directory, open '/home/me/package.json'npm 在目前目錄以及往上一層層找 package.json,一個都沒找到——你在一個不是專案的地方執行了 npm 命令。這發生在你站在 monorepo 的根上而專案在子資料夾裡、你站在複製下來的資料夾旁邊、或者你乾脆忘了 cd 的時候。答案是切到有 package.json 的那個資料夾,用 ls 看一眼最快。只有當你真的想在這裡開一個新專案時才用 npm init -y:在錯的資料夾裡跑,會留下一個多餘的 package.json,日後把工具搞糊塗。npm ERR! code ELIFECYCLEpackage.json 裡的某個 script 以非零碼結束了——ELIFECYCLE 是 npm 在外面包的一層殼,不是原因。原因是你的建置或測試指令稿幹了什麼,真正的錯誤行在這一行的上面。往上翻到第一條錯誤,或者把 npm ERR! <pkg>@<ver> <script>: 後面印出來的那條真命令手動跑一遍,就能看到不帶 npm 外殼的原始輸出。後面跟著的結束碼也能透露一點:1 是普通失敗,137 表示行程被殺掉了,通常是因為記憶體。gyp ERR! build error某個相依裡含有 C 或 C++ 程式碼,安裝時必須在你的機器上編譯,而這次編譯失敗了——node-gyp 需要編譯器和 python。這發生在完全沒裝編譯工具鏈的時候,或者這個套件比你的 node 老、標頭檔已經對不上的時候。裝上工具鏈:macOS 上用 xcode-select --install 裝命令列工具,Debian 或 Ubuntu 上裝 build-essential 和 python3,Windows 上裝 Visual Studio 的 C++ 工作負載。如果這個套件的新版本為你的 node 提供了預先編譯的二進位檔,升級這個套件比修編譯環境快得多。Error: Cannot find module 'express'node 沒能把這個名字解析到任何檔案:它不在 node_modules 裡,或者你是在一個沒有 node_modules 的目錄下執行的。這發生在你根本沒裝、裝到一半失敗留下半棵樹、或者這個套件在 devDependencies 裡而你用 --omit=dev 裝的時候。rm -rf node_modules && npm install 會從 lock 檔重建整棵樹;它安全、什麼都不丟,代價只是下載時間。但如果引號裡的名字是以 ./ 開頭的相對路徑,那就不是少了套件,而是你自己程式碼裡的路徑寫錯了。Module not found: Error: Can't resolve './components/Button' in '/app/src'打包器在那個路徑下找不到那個檔案——這是你自己寫的 import,不是套件的問題。它出現在相對路徑差了一層、副檔名漏了或寫錯、或者檔名的大小寫和 import 不一致的時候。最後一種最陰:macOS 和 Windows 的檔案系統不區分大小寫,所以在你機器上能跑,只在 Linux 的 CI 上崩。把那個目錄 ls 出來,和 import 一個字元一個字元地對,大小寫也要對。如果這個名字是套件而不是路徑,那就把它裝上;兩種情況下都不會刪掉任何東西。Error: error:0308010C:digital envelope routines::unsupportednode 17 換上了 OpenSSL 3,把一個老的雜湊演算法從預設裡去掉了,而仍然要用它的工具就在那次雜湊呼叫裡當場倒下。這幾乎總是一個老的 webpack 4,或者內含 webpack 4 的工具,跑在現代 node 上。export NODE_OPTIONS=--openssl-legacy-provider 只為那個行程重新打開老演算法,用於建置沒有害處。但這是在硬撐一個已死的工具:升到 webpack 5,或者升到你框架的目前版本,這個選項就完全不需要了。FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryV8 的堆到了上限,node 自己放棄了——不是作業系統把它殺了,是 node 判斷沒法再長下去而停住。原因是一次大的型別檢查或打包、在大專案上開著 source map 建置,或者像把所有東西都往陣列裡堆這樣的洩漏程式碼。export NODE_OPTIONS=--max-old-space-size=4096 把上限提到 4 GB,往往就夠了,但機器得真有這麼多記憶體;如果容器的限制更低,這次換成作業系統來殺行程,表現為結束碼 137。如果這個數字得一次次往上加,原因就是洩漏,而不是上限。npm WARN EBADENGINE Unsupported engine某個套件在 engines 裡宣告的 node 或 npm 版本範圍,你的環境不滿足——npm 只是警告,照樣裝上。它出現在你用的是系統套件管理器裝的舊 node,或者專案已經遷到比你 shell 裡更新的 node 的時候。用你的版本管理器把 node 切到印出來的範圍之內;該信的是專案的 .nvmrc 或 package.json 裡的 engines 欄位。預設它只是警告,但如果 .npmrc 裡有 engine-strict=true,同樣的條件就會變成中斷安裝的錯誤。zsh: command not found: tscshell 把 PATH 找了一遍,沒找到叫這個名字的可執行檔——安裝本身很可能是成功的。它出現在 npm 的全域 bin 目錄不在 PATH 上、你裝的是本機而不是全域所以可執行檔進了 node_modules/.bin、或者 shell 還快取著舊的 PATH 的時候。npx tsc --version 完全不動 PATH 就能跑本機那份,所以這一行就能把「沒裝上」和「不在 PATH 上」分開。如果確實是全域安裝,把 npm prefix -g 的輸出後面加上 /bin 這個路徑加進 PATH,然後重開一個 shell。

JavaScript15

瀏覽器和 node 的錯誤通常只告訴你什麼壞了,不告訴你那個值為什麼變成這樣——你讀了 undefined、它不是函式、它不是 JSON——所以該修的地方在拋錯那一行的上游;只用 ?. 和預設值把那一行按住,同樣的問題會在更遠的地方、以更難辨認的樣子回來。

Uncaught TypeError: Cannot read properties of undefined (reading 'name')點號左邊的值是 undefined,你卻去讀它的屬性;括號裡的名字是你想讀的屬性,所以壞掉的是它前面那個值。它來自還沒到達的回應、沒人傳進來的 prop、像 data.user.name 這樣多探了一層,或者對空陣列取 arr[0].id。如果這個值本來就可有可無,user?.name 能短路掉;但如果它本該存在,?. 只是把報錯換成一個 undefined,推給下一行去炸——去上游找它為什麼是 undefined。Chrome 78 之前同一個錯誤寫作 Cannot read property 'name' of undefined。TypeError: items.map is not a function這個名字存在,但它不是函式——如果它壓根不存在,訊息會改成說 undefined。典型原因是:你當成陣列的東西其實是物件或 NodeList;API 回傳的是 {data: [...]} 而不是陣列本身;或者模組預設匯出的形狀你猜錯了。別先猜,先印出來:一行 console.log(typeof items, items) 就能當場解決其中一半。是 NodeList 或 Set,Array.from(items) 包一下就好;但若它是 {data: [...]},包是錯的,正確做法是 items = res.data。RangeError: Maximum call stack size exceeded呼叫堆疊的深度超過了引擎允許的上限,而這通常不是遞迴太深,是遞迴停不下來。終止條件缺失或永遠到不了的函式、互相呼叫的兩個函式、對包含自身的物件做 JSON.stringify,在 React 裡則是算繪過程中修改了自己相依的狀態。主控台裡的呼叫堆疊會反覆出現那兩三個框——那一對就是環,基線條件該放在那裡。如果這段計算真的需要深度,把遞迴改寫成迴圈或顯式堆疊是唯一的路:瀏覽器的堆疊上限是抬不高的。SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON你交給 JSON.parse 的是 HTML,不是 JSON。訊息裡引出的 <!DOCTYPE 片段就是證據,它說明伺服器回傳的是一個 HTML 頁面——404、502,或者跳轉登入頁——所以真正的毛病在剖析之前,在那次請求上。典型原因:網址寫錯、開發伺服器的代理把 index.html 當成 API 回傳了、工作階段過期被重新導向到登入頁。呼叫 res.json() 之前先看 res.ok,不對就用 res.text() 讀出到底來了什麼;用 curl -s 直接打那個網址是最快的一眼。Chrome 111 和 Node 20 之前,同一個錯誤寫作 Unexpected token < in JSON at position 0。SyntaxError: Unexpected end of JSON input剖析器還在讀 JSON,文件就結束了;幾乎總是因為回應主體本來就是空的。對 204 No Content 或沒有回應主體的錯誤回應呼叫 res.json(),把回應主體讀了兩次而第二次拿到空,或者讀了一個寫到一半被截斷的檔案。先取文字、判斷是否為空,再剖析,才能得到準確的診斷——const t = await res.text(); if (!t) return null;。用 JSON.parse(text || 'null') 糊過去,會把空回應當成正常,所以先弄清伺服器為什麼發了空的回應主體。TypeError: Failed to fetch請求結束時沒有拿到任何回應,而瀏覽器故意隱去了原因:被 CORS 擋下、位址是死的、憑證不對、廣告封鎖擴充功能掐斷,都會印出同樣這幾個字。它是被拋出的錯誤而不是狀態,所以 404、500 根本到不了這裡——伺服器連開始應答都沒做到。主控台裡這一行的上方或 Network 面板裡,往往還有一條更具體的說明,先讀它;用 curl -i 直接打那個位址,能把「伺服器掛了」和「瀏覽器拒絕了」分開。Firefox 裡同樣的情形寫作 NetworkError when attempting to fetch resource。Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.請求到達了伺服器,回應也回來了,但那個回應裡沒有允許這個來源的標頭,於是瀏覽器拒絕把它交給你的 JavaScript。只有瀏覽器執行這條規則,所以同一個位址用 curl 就能通——看起來像是伺服器沒問題、只有瀏覽器壞了。要改的地方永遠在伺服器:給回應加上 Access-Control-Allow-Origin: http://localhost:3000。前端什麼也做不了;如果伺服器不是你的,經由自己的伺服器轉發就是唯一的路。Allow-Origin: * 對攜帶 cookie 的請求無效,那種情況必須寫出確切來源,並同時開啟 Allow-Credentials。SyntaxError: Cannot use import statement outside a modulenode 或瀏覽器把那個檔案當作 CommonJS 指令碼來讀,而裡面出現了 ESM 的關鍵字 import。除非 package.json 另有宣告,.js 檔案一律按 CommonJS 讀;在瀏覽器裡,則是 <script> 標籤少了 type="module" 時出同樣的事。npm pkg set type=module 把整個套件宣告為 ESM 就能解決,但從那一刻起這個套件裡所有 .js 都失去 require 和 __dirname,剩下的 CommonJS 檔案必須一起遷移——如果只想改一個檔案,把副檔名換成 .mjs 是更小、更安全的改動。ReferenceError: require is not defined in ES module scope, you can use import instead這正是上一條錯誤的鏡像——檔案被當作 ESM 讀,裡面卻有 CommonJS 的 require。它幾乎總是發生在剛給 package.json 加上 "type": "module"、而舊指令碼還留在目錄裡的時候。想讓那一個檔案留在舊世界,mv script.js script.cjs 是最小的改動;想把它遷過去,把 require 改成 import 的同時還得處理 __dirname 和 require.main === module,這兩個在 ESM 裡並不存在。Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/src/util' imported from /app/src/index.jsNode 執行 ESM 時,像瀏覽器那樣把 import 路徑當作字面的檔名,所以 './util' 不等於 './util.js',而是一個不存在的檔案。CommonJS 會替你試副檔名,而 TypeScript 編譯後也原樣保留不帶副檔名的 import,所以用 tsc 建置的專案第一次用 node 跑起來時會成堆地報這個。給相對路徑的 import 補上 .js 副檔名;在 TypeScript 檔案裡也照樣寫 './util.js',因為 Node 讀到的是編譯產物。來自 node_modules 的套件名不加副檔名。ReferenceError: window is not defined這段程式碼是在伺服器的 Node 裡跑的,不是在瀏覽器裡。Node 沒有 window、沒有 document、也沒有 localStorage,所以在 Next.js、Nuxt 這類先做伺服器端算繪的框架裡,模組載入階段就去碰 window,就會停在這裡——往往是元件外面的那一行,或者僅限瀏覽器的程式庫的 import 本身。如果這活兒只屬於瀏覽器,用 typeof window !== 'undefined' 包住,或者更好,把它搬進 useEffect,因為 effect 只在瀏覽器裡執行。若整個程式庫都只能在瀏覽器用,Next.js 裡可以用 dynamic(() => import('./C'), { ssr: false }) 把它從伺服器端算繪裡排除,代價是這部分不會出現在伺服器產生的 HTML 中。Hydration failed because the initial UI does not match what was rendered on the server伺服器發來的 HTML 與瀏覽器首次算繪出來的結果不一致,React 無法把兩者對接。在算繪過程中用 new Date() 或 Math.random()、讀 localStorage、按 window 寬度畫不同的東西,都注定給出兩個不同的答案;瀏覽器會悄悄糾正的標籤也一樣,比如 <p> 裡套 <div>。規矩是:首次算繪要畫出和伺服器完全相同的東西,差異留到 useEffect 跑完之後再改。代價是那部分會晚一瞬間出現,並且不在伺服器產生的 HTML 裡。React 19 把同樣的情形寫作 "the server rendered HTML didn't match the client",還會印出哪裡不一致的 diff。Objects are not valid as a React child (found: object with keys {name, id})你交給 React 算繪的值是一個物件,而不是字串或數字,括號裡的鍵列表告訴你它是哪個物件。通常寫著 {user} 的地方本該是 {user.name},或者你想把整個回應直接畫出來。如果看到的不是鍵列表而是 found: [object Promise],原因就換了:你把一個 async 函式的結果沒 await 就算繪了。解決辦法是挑出要顯示的欄位;除錯時用 JSON.stringify(user) 看一眼可以,但不要把它留在介面上。Each child in a list should have a unique "key" prop.作為列表算繪的兄弟元素沒有 key,於是下一次算繪時 React 無法把它們和同一批資料對應起來。你用 items.map(...) 造元素時漏了 key;這只是警告,介面照樣畫得出來,但列表一變它就以無聲的問題回來——症狀是輸入框的值或動畫留在了錯誤的那一行。用資料裡穩定的 id 作 key。用陣列索引當 key,只有在順序永不改變、也從不在中間插入或刪除的列表裡才安全,否則它重現的幾乎就是「沒有 key」的那個問題。從 React 19 起,前面的 Warning: 不再出現。Too many re-renders. React limits the number of renders to prevent an infinite loop.一次算繪改了狀態,這個狀態又觸發了下一次算繪,React 為了不讓它無限下去把這個環截斷了。幾乎總是「該傳函式的地方把函式呼叫了」:onClick={setOpen(true)} 每次算繪都會立刻執行,正確寫法是 onClick={() => setOpen(true)}。另外兩種常見形態是在算繪函式主體裡直接改狀態,以及 useEffect 的相依陣列裡放了它自己會更新的值。把改狀態的動作搬進事件處理函式或 useEffect;如果這個值算繪時算一下就有,不妨反問它是否需要成為狀態。

Python18

Python 的 traceback 把答案劈成兩半——最後一行說「錯了什麼」,上面的框說「錯在哪裡」——所以只讀最後一行,你會拿到名字卻丟掉位置;而 NoneType、KeyError 這類「值不存在」的錯誤,原因幾乎總在崩潰那一框的上面。

ModuleNotFoundError: No module named 'requests'Python 把 sys.path 上的目錄都翻了一遍,沒有這個名字的模組。要麼根本沒安裝,要麼安裝到了另一個解譯器裡——在虛擬環境外面 pip install、又在裡面執行,必然是這個結果。寫成 python -m pip install 會安裝進你真正在跑的那個解譯器,這個錯位就消失了。error: externally-managed-environment這個 Python 是作業系統或 Homebrew 管著的,pip 拒絕往裡面寫(PEP 668)。你是對系統解譯器執行了 pip install;這條訊息是在叫你建虛擬環境。--break-system-packages 就像它名字說的那樣,可能讓 apt 管理的檔案處於損壞狀態,所以正確做法是建一個 .venv 並安裝在裡面。ImportError: cannot import name 'User' from partially initialized module 'models' (most likely due to a circular import)兩個模組互相 import,於是你從一個只載入了一半的模組裡取名字。它出自 A 引 B、B 又引 A 的結構,在剛拆分檔案、某個型別註記把 import 又拉回去時最常見。把 from y import Thing 改成 import y,在函式裡寫 y.Thing:查找延到呼叫時,這個環就不再要緊了。IndentationError: unexpected indent這一行比上一行縮排更深,而上面並沒有任何東西開啟了一個能解釋這層縮排的程式區塊。通常是你貼進來的程式碼自帶了前導空白,或者你刪掉了 if 那一行,把它的主體留在了縮排裡。把報錯那一行開頭的空白去掉即可;看不出差別時,用 cat -A 印出那一行,空白和定位字元就現形了。TabError: inconsistent use of tabs and spaces in indentation同一個程式區塊裡混用了定位字元和空白,Python 無法判定哪一行更深。這多半是從會插入定位字元的編輯器裡貼上,或者兩個人用不同設定改同一個檔案;螢幕上兩種縮排看起來一模一樣,靠眼睛找不出來。python -m tabnanny app.py 會印出出錯的行號,剩下的就是在編輯器裡把整個檔案統一成四個空白。SyntaxError: invalid syntax剖析器在那個位置無法把內容讀成一條敘述,而原因往往在它指出的那一行之前,而不是那一行本身。沒閉合的括號或引號、漏掉的冒號、Python 2 的 print "x" 是常見原因——括號沒閉合時,剖析器會往後走好幾行才放棄。python -m py_compile app.py 可以不執行只檢查語法;3.10 之後的提示具體得多,所以升級 Python 本身就是一種診斷。TypeError: 'NoneType' object is not subscriptable你用 [...] 取值的那個東西是 None。上游有什麼悄悄回傳了 None:缺鍵的 dict.get()、沒比對上的 re.match,或者在實際走過的分支上沒有 return 的函式。不要在崩潰點加判斷,往上游找它為什麼是 None——一個跳過 None 的 if 只是把同一個錯誤挪到下一行。AttributeError: 'NoneType' object has no attribute 'get'點號左邊那個物件是 None,所以你要的屬性或方法不存在。它和 nonetype-not-subscriptable 同源,尤其常出現在直接接著一個失敗時回傳 None 的函式寫下去的時候,比如 BeautifulSoup 的 find() 或 re.search()。先把那個函式在找什麼印出來;如果「找不到」本身是合法情形,就把這一支單獨處理。KeyError: 'name'這個鍵不在 dict 裡,引號中的字串正是它去找的鍵——先從這裡查,通常是拼字錯誤、大小寫不同,或者 API 回傳的結構和你以為的不一樣。如果這個值確實可有可無,d.get("name") 會回傳 None,d.get("name", 0) 還能給預設值;但對必須存在的值也用 .get,就是用一個 None 換掉了報錯,它會流進程式碼深處,在很遠的地方才炸。IndexError: list index out of range你要的位置串列裡沒有。長度為 n 時最大索引是 n-1,所以 range(len(x) + 1) 和 x[len(x)] 必然停在這裡;對空串列取 x[0] 也是同一個錯誤——上面某個過濾或 split 什麼都沒留下時最常見。改成 for item in items: 遍歷,這一類問題會從結構上消失;真的需要位置時用 enumerate(items)。ValueError: invalid literal for int() with base 10: '3.5'你給 int() 的是一個它無法讀成整數的字串,引號裡的內容就是那個字串。像 '3.5' 這樣帶小數點、空字串、結尾帶換行的輸入,或 '1,000' 這樣帶千位分隔符號,都是常見原因。帶小數點的可以用 int(float(s)) 兩步轉換,但它是截斷小數而不是四捨五入;只要是人手輸入的值,用 try/except ValueError 包起來才是誠實的做法。UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte你把一個檔案或位元組串按 UTF-8 讀,卻碰到了 UTF-8 不允許的位元組;訊息裡同時給出了位置和那個位元組。多半是檔案其實是 Windows 那邊的 cp1252 或別的舊編碼,或者根本不是文字——是圖片、zip,或者忘了解壓縮的 gzip 資料。正確做法是查出真實編碼並用 encoding= 傳進去;errors="replace" 能讓讀取走完,但會把那些位元組換成問號,資料從此就悄悄壞掉了。ZeroDivisionError: division by zero有東西被零除了——實際上除數很少是字面的 0,而是數出來等於 0 的計數。算平均值時串列恰好為空,或者過濾條件一個都沒比對上,是最經典的來源:它在開發資料上永遠通過,到線上才第一次炸。除之前先看分母是不是 0,並決定 0 在這裡意味著什麼;回傳 0、回傳 None、讓例外繼續往上拋,是三個不同的決定,各有各適用的場合。RecursionError: maximum recursion depth exceeded函式自我呼叫的深度超過了預設的 1000 層。相比真的需要那麼深的計算,更多情況是終止條件缺失或永遠到不了;間接遞迴也算,比如在 __getattr__ 或 property 裡又去讀自己的屬性。先在 traceback 裡找出反覆出現的那兩三個框,把基線條件修好。sys.setrecursionlimit() 確實能抬高上限,但它同時讓你越過真正的 C 堆疊,那時 Python 會直接死掉而不是拋出例外——把遞迴改寫成迴圈才是安全的答案。UnboundLocalError: cannot access local variable 'count' where it is not associated with a value只要函式主體裡任何一處給這個名字賦了值,Python 就把它當作函式的區域變數——而你在賦值執行之前就讀了它。當你期望讀到外層同名的值時,或者第一次寫 count += 1 這類先讀後寫的運算時,就會碰到。通常的答案是在函式裡先 count = 0;如果確實要改模組層級的值,global count 能做到,代價是這個值由誰改寫變得難以追蹤。3.10 之前同一個錯誤寫作 'local variable referenced before assignment'。TypeError: greet() takes 1 positional argument but 2 were given你傳的參數比函式接受的多了一個;當數量正好差一個時,幾乎總是 self。把類別裡的方法定義成 def greet(name):,呼叫 obj.greet("x") 時 Python 會把 obj 當作第一個參數傳進去,於是就成了兩個。如果它是方法,改成 def greet(self, name): 讓它接收 self;如果它根本不需要實例,就加上 @staticmethod。PermissionError: [Errno 13] Permission denied作業系統拒絕了對那個路徑的這個操作。你在往別人擁有的地方寫,比如 /var/log 或 /usr;或者容器裡掛載卷的 UID 和容器使用者不一致;或者——最容易被漏掉的——你缺的是所在目錄的寫入權限而不是檔案的,而新建檔案需要的正是目錄權限。先用 ls -ld 看擁有者和權限位;在動用 sudo 之前,先考慮把路徑換到你本來就能寫的地方:用 sudo 建出來的檔案,下次不帶 sudo 開啟時會再報同一個錯。OSError: [Errno 98] Address already in usebind 失敗是因為那個連接埠已經被另一個行程占著。可能上一個伺服器沒有真正結束,可能自動重載啟了兩份,也可能某個 Docker 容器已經發布了同一個連接埠。lsof -i :8000 會給出占用它的 PID,你只清掉那一個即可——在 kill -9 之前先看清行程名稱。errno 因系統而異:Linux 這裡印出 98,macOS 印出 48。

建置與型別10

建置階段的錯誤是工具在執行之前就先拒絕了,所以它們出現的那一刻並不弄壞什麼——但它們問你兩個問題:你要把型別或路徑改成和真實結構一致,還是用一個 as any、一條抑制註解讓那一行過去?也正是在這裡,那些只在你自己機器上成立的事情——大小寫、副檔名、環境變數——第一次顯形。

error TS2307: Cannot find module 'lodash' or its corresponding type declarations.tsc 既沒找到那個模組本身,也沒找到它的型別宣告,而這條訊息同時說了兩件事,這一點很關鍵——可能套件壓根沒安裝,也可能安裝了但它不帶型別。如果確實安裝了,那缺的就是型別:npm i -D @types/lodash 裝上配套的型別套件;不過如今很多套件自帶型別,根本沒有對應的 @types。如果這個錯出現在相對路徑的 import 上,就去看大小寫和 tsconfig 裡的 paths——指向你自己檔案的路徑和 @types 毫無關係。error TS2339: Property 'user' does not exist on type 'Request'.那個型別裡沒有宣告這個屬性。它來自往程式庫的型別上加一個它沒有的欄位(往 Express 的 Request 上掛 user 是典型)、拼字錯誤,或者在未縮小範圍的聯集型別上讀只屬於其中一支的屬性。把型別改成和真實結構一致才是答案;是聯集型別的話,用 'x' in y 或判別欄位縮小範圍,在那一支裡存取就打開了。用 as any 壓掉只是堵住編譯器的嘴:那個位置從此連拼錯的屬性名也會放過。error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.函式接受的型別和你傳進去的型別不一致,訊息按順序寫明了兩者——前面是你給的,後面是它要的。最常見的情形是表單欄位、URL 查詢字串或 JSON 裡的值以字串形態進到了要數字的位置。在邊界處轉換一次是答案,但 Number(value) 失敗時回傳的是 NaN 而不是拋錯,所以必須攔住這個 NaN 別往下流——Number.isFinite(n) 就是該放在這裡的檢查。error TS18048: 'user' is possibly 'undefined'.這個值可能是 undefined,而你沒有處理這種可能;這是 tsc 替你提前抓住了一個將來的 Cannot read properties of undefined。它出現在可選欄位、陣列 find() 的結果,以及可能沒設定的環境變數上。解決辦法是決定「它不存在時怎麼辦」:在函式開頭用 if (!user) return null 早退,或者用 user?.name 短路。寫成 user!.name 壓掉,等於宣告「我保證它存在」——保證一錯,它就以執行時例外回來,而且不留下檢查本會留下的任何安全網。error TS7006: Parameter 'req' implicitly has an 'any' type.這個參數沒寫型別,tsc 也沒有可推斷的依據,於是它隱含變成了 any——而 noImplicitAny 把這當作錯誤。它出現在從 JavaScript 遷過來的檔案裡,以及把回呼單獨提到變數裡的時候;相反,像 items.map(x => ...) 這樣內嵌寫的回呼是有上下文的,tsc 能推斷出來,就不會報這個。在那裡把型別寫上就是解法。你也可以明確寫 any,但那是決定把這個參數的檢查整個關掉——當你不知道它是什麼時,unknown 更誠實,並且強迫你在使用前先縮小範圍。Parsing error: Unexpected tokenESLint 是在讀語法的階段就停下的,還沒來得及把任何規則套到這個檔案上。相比程式碼真的壞了,更多情況是剖析器不認識這套語法——用預設剖析器讀 TypeScript 檔案、從未開啟 JSX、把裝飾器或很新的語法交給了舊剖析器。對準那個檔案執行 npx eslint --print-config app.ts,會直接印出真正生效的 parser 和 parserOptions,所以先看它,別猜。TypeScript 需要 @typescript-eslint/parser;如果這個檔案本就不該被檢查,把它寫進 ignores 才是正確答案。You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.這是 webpack 在 Module parse failed 之後補上的那一行,意思是它想把那個檔案當 JavaScript 讀,而它不是 JavaScript。要麼你 import 了需要轉換的東西——TypeScript、JSX、CSS、圖片、.vue 檔案——而 module.rules 裡沒有對應規則;要麼 node_modules 裡某個套件發布的是未編譯的原始碼,而這個目錄正躺在你的 exclude 裡。在 module.rules 裡為那個副檔名加一條 loader 規則就是解法,而卡在哪個檔案,訊息上方印出的路徑會告訴你。為了讓它消失而刪掉 exclude: /node_modules/,會讓整個建置明顯變慢;只為那一個套件開例外才是更好的交易。Failed to resolve import "./utils" from "src/main.ts". Does the file exist?那個路徑下什麼也沒找到,而它一起印出的 "Does the file exist?" 指向真正值得查的三件事:大小寫、副檔名,以及 tsconfig 或 vite.config 裡的別名。macOS 和 Windows 的檔案系統不分大小寫,所以寫 ./Utils 在你機器上照樣跑,直到在 Linux 的 CI 和部署上才第一次崩——這個錯誤是「我這兒明明是好的」最常見的真身。git ls-files src | grep -i utils 能看出倉庫裡實際存的是哪種拼寫。若用的是 @/utils 這類別名,vite.config 的 resolve.alias 和 tsconfig 的 paths 必須一致;只改一處,編輯器安靜了,建置照舊失敗。You're importing a component that needs useState. This React hook only works in a client component.在 App Router 裡每個元件預設都是伺服器元件,而某個伺服器元件 import 了一個使用 useState 這類只在瀏覽器裡才有意義的 hook 的檔案。指示詞就是檔案最頂端的一行 'use client',加上它會把這個檔案以及它 import 的一切都拉進用戶端套件——所以只給真正需要狀態的最小那塊加,而不是整頁加,才是便宜的選擇。反方向上,把結構拆成「用戶端元件把伺服器元件當作 children 接收」,就能把取資料的活留在伺服器。Next.js 13 裡同樣的情形寫作 "It only works in a Client Component but none of its parents are marked with \"use client\""。The engine "node" is incompatible with this module. Expected version ">=20"你要安裝的套件在 package.json 的 engines 欄位裡宣告了它需要的 node 版本,而你目前用的版本落在這個範圍之外。後面跟著的 Expected 和 Got 把要求和你的版本並排列出,看這兩行就夠了——如果只有 CI 上報這個,那就是它的 node 版本和你機器上的不一樣。用 nvm install 20 && nvm use 20 升上去是正面的答案,並把同一個版本寫進 .nvmrc 和 CI 設定,下次才不會再岔開。yarn 的 --ignore-engines 能硬闖過去,但它只是抹掉警告,並不製造相容性:那個套件一旦用了更新的語法,就改成在執行時以語法錯誤炸掉。npm 把同樣的情形報成 EBADENGINE 警告,預設並不阻止安裝。

Docker12

docker 的錯誤要先歸到某一層才讀得懂——用戶端連不上守護行程、映像檔倉庫拒絕你、建置過程中某條 RUN 失敗、容器一起來就死掉,是四種不同的問題——而在建置和執行這兩層,那行字本身並不是原因:原因在裡面那條命令留下的輸出裡,各層修復的代價也不一樣。

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?docker 命令自己什麼都不做,它透過那個 socket 請求守護行程,而沒有人應答。要麼守護行程沒在執行,要麼 macOS/Windows 上的 Docker Desktop 還沒啟動完,要麼在 Linux 上你的使用者不在 docker 群組裡、無權開啟這個 socket;這三種情況都只印出這一行。Linux 上用 sudo systemctl start docker 啟動;用 Desktop 就先把應用程式開啟。如果是權限問題,sudo usermod -aG docker $USER 再重新登入即可——但要知道這個群組實際上等同於 root。Bind for 0.0.0.0:8080 failed: port is already allocated你用 -p 要占主機的那個連接埠,而它已經被另一個容器或行程占著。最常見的是之前不加 --rm 啟動的容器還留在那裡(停著或跑著),或者 compose 還攥著一個舊容器。docker ps --filter publish=8080 會直接指出發布該連接埠的容器;如果其實是主機上的普通程式而不是容器,用 lsof -i :8080 找。只改主機一側,比如 -p 8081:80,能讓你先跑起來,但舊容器不清掉,下次還是同一場撞車。Conflict. The container name "/api" is already in use by container你用 --name 給的名字已經被另一個容器占了。名字在停止的容器和執行中的容器之間是共用唯一的,所以多半是昨天掛掉的那個容器還攥著它——docker ps 裡看不到,只有 docker ps -a 能看到。docker rm -f api 會釋放這個名字,同時也會把那個容器的可寫層一起銷毀(具名資料卷會保留)。一次性用的容器,一開始就用 docker run --rm 啟動,這種情況根本不會發生。failed to register layer: Error processing tar file(exit status 1): no space left on device磁碟上沒有空間可以展開映像層了。通常並不是專案大,而是 docker 還攥著幾個月的舊映像、建置快取,以及你已刪除容器留下的資料卷。docker system df 會先告訴你映像、容器、資料卷和建置快取各占多少、其中多少可回收,刪任何東西之前先讀它。docker system prune 會刪掉停止的容器、懸空映像、未使用的網路和建置快取——快取一沒,下一次建置會明顯變慢;再加 --volumes 還會刪掉沒掛在容器上的資料卷,人們丟資料庫正是丟在這裡。pull access denied for myapp, repository does not exist or may require 'docker login'映像檔倉庫不肯把那個倉庫給你看,而關鍵在於這條訊息同時涵蓋兩種原因:名字不存在,或者存在但你無權看到。要麼你沒有為私有倉庫登入,要麼名字缺了使用者或組織(myapp 和 myorg/myapp 是兩個不同的倉庫),要麼名字本身就寫錯了。私有映像就對那個倉庫 docker login;如果它本該是公開的,就再核一遍拼字。映像檔倉庫故意不區分「不存在」和「不給看」——因為一個名字是否存在本身就是資訊。manifest for myapp:v2 not found: manifest unknown倉庫找到了,但那個標籤不在裡面。和 pull access denied 不同,這條訊息說明名字是對的,於是只剩標籤要查——被刪掉或挪走的標籤、只推了 latest 而從未建立 v2 的流水線,或者那個標籤下沒有你這個架構的映像。docker manifest inspect myapp:v2 能確認標籤到底能否解析、裡面有哪些 platform。為了先跑起來而改用 latest,代價是一個無法重現的建置,不如把標籤改對。unauthorized: incorrect username or password映像檔倉庫拒絕了你發過去的憑證。比打錯密碼更常見的,是把密碼發給了一個早已不接受帳號密碼的倉庫——Docker Hub 開啟兩步驗證後只收存取權杖,而 GitHub、GitLab 的倉庫從一開始就只要權杖或部署金鑰。docker logout 清掉存下的憑證,然後用權杖重新 docker login。寫成 echo $TOKEN | docker login -u user --password-stdin,權杖就不會留在 shell 歷史裡。failed to solve: process "/bin/sh -c npm ci" did not complete successfully: exit code: 1Dockerfile 裡那條 RUN 以非零狀態結束了。這一行帶的只有「哪條命令失敗、結束碼是幾」,真正的原因在它上面那條命令自己的輸出裡,而 BuildKit 一旦某步結束就傾向於把它摺疊起來。用 docker build --progress=plain 重跑,會把每一步的輸出完整印出來;如果是某個快取層藏住了更早的失敗,再加 --no-cache——代價是從第一步開始整個重建。exit code: 1 不是原因,只是「這條命令失敗了」這個事實。COPY failed: file not found in build context or excluded by .dockerignoreCOPY 只能從建置上下文裡取檔案,而那個檔案不在其中。上下文是 docker build 的最後一個參數,所以通常是你用 ../x 指向了上一層目錄,或者 .dockerignore 把那個檔案濾掉了——忽略了 node_modules 或 *.env,卻又要 COPY 其中的檔案,是最典型的。這條訊息同時點出兩種原因,所以先 cat .dockerignore,再按上下文根目錄重寫路徑。靠擴大上下文來解決,意味著整個目錄都要傳給守護行程,每次建置都會變慢。exec /usr/local/bin/entrypoint.sh: exec format error核心認不出那個可執行檔的標頭,而如今這幾乎總是架構不相符——在 Apple Silicon 上建置的 arm64 映像跑在 amd64 主機上,或者反過來。shell 指令碼少了第一行 #!/bin/sh 時也會印出同樣的字。用 docker build --platform=linux/amd64 建置是常見做法,但它不是消除不相符,而是用模擬把它蓋住:經過 QEMU 的執行比原生慢好幾倍。要長期使用的映像,用 buildx 同時建置兩種架構,才是執行時不付代價的路。standard_init_linux.go: exec user process caused: no such file or directory容器要執行它的入口點,核心回答「no such file」——陷阱在於那個檔案明明就在那裡。指令碼的行尾是 CRLF,於是第一行被讀成 #!/bin/sh\r,核心便去找一個名字真的叫 sh\r 的解譯器;在 Windows 上複製、或者開著 git 的 autocrlf,正是這個結果。dos2unix entrypoint.sh 能轉換那一個檔案(沒有它就用 sed -i 's/\r$//' entrypoint.sh),而在 .gitattributes 裡寫一行 *.sh text eol=lf 能防止它再來。在 #! 裡寫了映像中並不存在的解譯器——比如 alpine 映像裡的 bash——也會給出同樣的字。OCI runtime create failed: exec: "bash": executable file not found in $PATH: unknown容器建出來了,但你讓它執行的程式在容器內部的 $PATH 裡找不到。基於 alpine 的映像只帶 sh 而沒有 bash,所以 docker run -it myapp bash 或者 CMD ["bash", ...] 恰好就以這個錯誤收場——你以為映像裡有、其實只存在於主機上的任何工具也一樣。docker run --rm -it myapp sh 能先給你一個 shell,看清這個映像裡到底有什麼。如果確實需要 bash,在 Dockerfile 裡 RUN apk add --no-cache bash 就能裝上,代價是映像變大。

怎麼讀報錯

  • 從第一行往下讀。越往下越是工具內部的事,起因通常寫在最上面。
  • 有檔名和行號就從那裡查——不是堆疊最上面那一格,而是最上面那條提到你自己寫的檔案的行。
  • 把報錯原文照樣去搜,但先去掉你自己的路徑和變數名,正是那些讓搜尋比對不上。
  • 同一種情況在不同版本裡措辭不同。結果不對頭,就把版本號一起加進查詢。
  • 貼上修法之前,先確認它會丟掉什麼。這裡面有些是不能收回的。

常見問題

Q. 為什麼報錯原文不翻譯?

因為你要拿它去搜。工具輸出的是英文,翻譯過的原文什麼也搜不到。跟著語言變的只有含義和處理辦法。

Q. 我螢幕上的措辭略有不同。

工具在不同版本裡會改措辭。去掉你自己的路徑和名字後骨架一致,就是同一個報錯。版本不同就把版本號一起加進搜尋。

Q. 修法可以直接跑嗎?

先看它會丟什麼。git reset --hard、強制推送和 docker system prune 刪的東西回不來——凡是這類,條目裡都寫了。

Q. 這裡沒有的報錯怎麼查?

把報錯裡你自己的路徑、名字和數字去掉,只搜剩下的部分。那部分是工具作者寫的句子,用它搜才對得上。