Skip to content

fix(drive): report files over the 100 MiB limit as FILE_TOO_LARGE without uploading - #136

Merged
pocketsharkk merged 6 commits into
mainfrom
claude/ship-wspc-todo-e35b75
Sep 18, 2026
Merged

pocketsharkk merged 6 commits into
mainfrom
claude/ship-wspc-todo-e35b75

Conversation

@pocketsharkk

@pocketsharkk pocketsharkk commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

使用者遇到的問題

Library 裡有一個 291 MB 的 heart-rate.jsonl,WSPC Drive 一直顯示 DRIVE_PATH_ERROR: HTTP 413,看起來像路徑出錯,其實是檔案超過 Drive 的單檔上限。更糟的是,這個檔案每天 append,每次變動 CLI 都會把整個 291 MB 讀進記憶體、上傳、再被拒收一次。

背景

問題出在哪裡

  1. Edge 在 worker 之前擋下超大 body,回的是沒有 JSON code 的 413。driveHttpError() 只從 body 取 code:

    const code = errorCode(payload)

    沒有 code 時 drivePathErrorSummary() 退回 DRIVE_PATH_ERROR。worker 自己拒收時會帶 FILE_TOO_LARGE,所以在小於 edge 上限、大於 100 MiB 的區間看不出問題,只有真正很大的檔案才會掉到 DRIVE_PATH_ERROR。

  2. runDriveSyncOnce() 在上傳前完全不看檔案大小,決策為 upload 就走 readStableUploadBody() 整檔讀進記憶體再送出。

修法

retry.ts:413 且 body 沒有 code 時補上 FILE_TOO_LARGE;有 code 則沿用 server 的。

// The edge rejects oversized bodies with a bare 413 before the worker adds a code.
const code = errorCode(payload) ?? (response.status === 413 ? "FILE_TOO_LARGE" : undefined)

sync.ts:在 Permanent Upload Rejection 迴圈之前、pendingPaths 計算之前,對不在 movedPaths 的 path 檢查本輪 scan 的 size_bytes:

for (const path of paths) {
  const sizeBytes = localFiles[path]?.size_bytes
  if (movedPaths.has(path) || sizeBytes === undefined || sizeBytes <= DRIVE_MAX_FILE_SIZE_BYTES) continue
  const action = decideDriveAction(state.entries[path], localFiles[path], remoteFiles[path])
  if (action.type !== "upload_create" && action.type !== "upload_update") continue
  rejectedPaths.add(path)
  // recordDrivePathError(..., { appendPathResult: true, debug, op: "file_too_large", pathError })
}

為什麼這樣寫:

  • 放進 rejectedPaths:沿用拒收略過集合(spec 允許實作者決定),該 path 自動排除於 pendingPaths 與 progress total,也就不會進 executeDrivePathAction() 讀檔。
  • 只在決策為 upload_create/upload_update 時判定:conflict 照一般流程(spec 明訂不預檢)。
  • 放在拒收迴圈之前:舊版留下的 DRIVE_PATH_ERROR 拒收紀錄仍由既有 CLI version 規則刪除,之後改報 FILE_TOO_LARGE。兩者同時命中時,recordDrivePathError() 本身依 path 去重,所以拒收迴圈不需要額外略過(有測試守護 errors === 1)。
  • 不寫 state.json:每輪重新判定,縮小、刪除、改名、ignore 後自然恢復。
  • 常數維持 module-private,旁註 server 來源;server 調整上限時須同步發 CLI。

這個 PR 不修什麼

  • 超過 100 MiB 的檔案仍然無法同步;這只是停止無謂上傳並講清楚原因。大檔同步需要 server 產品決策,須先在 sadcoderlabs/wspc 立 spec。
  • Conflict 流程不做大小預檢,只靠 413 對應拿到 FILE_TOO_LARGE。
  • Scanner 對大檔計算 sha256 的本機 CPU 成本不變。
  • WSPC Drive 內附 CLI 升級與文案(refactor(cli): route JSON parse errors through dispatch #92 與後續 bump PR)。

已知副作用

  • 超大檔每一輪都會回報一筆 FILE_TOO_LARGE path error、sync once exit 1,直到使用者處理該檔(spec 預期行為)。
  • 任何 Drive API 的 bare 413(不只上傳)都會帶 code: "FILE_TOO_LARGE",符合 spec。

測試

依 spec 四個切片,每片先寫失敗測試(test/handwritten/drive/retry.test.ts、test/handwritten/drive/sync.test.ts,大檔用 truncate 建 sparse file):

AC 測試 改動前
6 maps a 413 without a code to FILE_TOO_LARGE/keeps the server code on a 413 AssertionError: expected undefined to be 'FILE_TOO_LARGE'
6 reports a bare upload 413 as FILE_TOO_LARGE and remembers that code path error code 為 DRIVE_PATH_ERROR
1 never reads or uploads an oversized file and reports it every round expected [] to deeply equal [ { path: 'big.bin', …(3) } ](檔案被上傳)
2 leaves an oversized file out of progress and syncs other files expected Set{ 2 } to deeply equal Set{ 1 }
3 uploads a file of exactly 100 MiB(自訂 uploadFile 只記 byteLength) 切片 2 後即通過;把判定改成 >= 的 mutation 會紅
4 stops uploading a synced file once it grows over the limit 切片 2 已涵蓋;只判 upload_create 的 mutation 會紅
5 shrink/deleted/excluded/rename 四個恢復測試 移除判定的 mutation 下 8 個 oversized 測試全紅
7 replaces a rejection recorded by an older CLI with FILE_TOO_LARGE、reports an oversized file once when a current rejection also matches 同上

本機 npm run typecheck、npm test(66 files/799 tests)、npm run build 通過。

決定後回報(spec 要求):sparse file 測試單筆最長約 0.64 秒,不需替代做法;debug.log event 欄位維持 path、op、code、message。

Review 結果

  • Repository 規範:符合。TDD 每片先紅後綠、conventional commits、未動 src/generated//path-executor.ts/state.ts/watch.ts、只留說明 why 的英文註解、無 null。
  • Spec 契約:AC 1–7 皆有自動化測試;path_errors shape、state.json schema、retryable 語意不變。設計判斷(非違規):常數未 export(spec 為「建議」且無 importer);拒收迴圈的重複回報交給 recordDrivePathError() 的既有去重(spec 已知陷阱列出的兩種處理之一)。
  • Ponytail review:移除多餘的拒收迴圈略過判斷與一個與 AC 2 重複的測試。

未完成驗收

  • AC 8:CI typecheck/test/build(本機已通過,待 PR CI)。
  • AC 9(post-release,非 merge gate):需 CLI 發佈、sadcoderlabs/wspc-drive#92 merge、WSPC Drive 內附 CLI 升級後,在 live library 以 mkfile -n 101m 手動驗證並回報在 Todo comment。

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@pocketsharkk
pocketsharkk merged commit 5e6a852 into main Sep 18, 2026
3 checks passed
@pocketsharkk
pocketsharkk deleted the claude/ship-wspc-todo-e35b75 branch September 18, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant