OUT-3598: surface Dropbox error_summary on content-endpoint failures#99
Conversation
…ures The 409 from `_downloadFile` was opaque in Sentry because the manual fetch path threw `DropboxResponseError` with a hand-rolled `error_summary` and discarded the response body. Read the JSON body and forward Dropbox's actual `error_summary` / `error` shape so future failures show which 409 it is (`path/not_found`, `path/restricted_content`, `unsupported_file`, etc.). Adjacent cleanups uncovered during review: - replace deep `node_modules/copilot-node-sdk/dist/codegen` import with a structural `isCopilotApiError` guard in `CopilotAPI.ts` - defensive optional chaining on `error.error.error.path['.tag']` in `createAndUploadFileInDropbox`'s catch (the catch wraps two call sites with different 409 body shapes; non-matching ones used to TypeError). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR surfaces real Dropbox Confidence Score: 5/5Safe to merge — no P0 or P1 findings; previously flagged concerns are resolved by the refactoring. All three files show targeted, well-reasoned changes. The try/catch split directly fixes the misrouting hazard raised in the prior review thread. Optional chaining on No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller
participant Sync as SyncService
participant DBX as DropboxClient (SDK)
participant Content as DropboxClient (Content API)
participant Dropbox
Caller->>Sync: createAndUploadFileInDropbox(file, dbxFilePath, fileType)
Note over Sync,DBX: Isolated try/catch — only filesGetMetadata
Sync->>DBX: filesGetMetadata(dbxFilePath)
alt 200 OK
DBX-->>Sync: existing metadata
alt .tag === FOLDER
Sync-->>Caller: { dbxFileId }
else .tag === FILE
Sync->>DBX: filesMoveV2(old → timestamped)
Sync->>Content: _uploadFile(file, dbxFilePath)
Content->>Dropbox: POST /files/upload (stream)
alt 200 OK
Dropbox-->>Content: FileMetadata JSON
Content-->>Sync: DropboxFileMetadata
Sync-->>Caller: { dbxFileId, contentHash }
else 4xx error
Content->>Content: buildDropboxResponseError(response)
Note over Content: reads JSON body → DropboxResponseError with real error_summary
Content-->>Sync: throws DropboxResponseError
Sync-->>Caller: propagates (uncaught)
end
end
else 409 not_found
Note over Sync: swallow — existing stays undefined
alt fileType === FOLDER
Sync->>DBX: filesCreateFolderV2(dbxFilePath)
DBX-->>Sync: folderMetadata
Sync-->>Caller: { dbxFileId }
else fileType === FILE
Sync->>Content: _uploadFile(file, dbxFilePath)
Content->>Dropbox: POST /files/upload (stream)
Content-->>Sync: DropboxFileMetadata or throws
Sync-->>Caller: { dbxFileId, contentHash } or propagates
end
else other error
DBX-->>Sync: throws
Sync-->>Caller: propagates
end
Reviews (2): Last reviewed commit: "fix(OUT-3598): narrow not_found catch an..." | Re-trigger Greptile |
Two follow-ups from review feedback on 0426a12: - `createAndUploadFileInDropbox` previously wrapped both `filesGetMetadata` and the rename-then-reupload path in a single try/catch keyed on a 409/`path/not_found` body. Now that `_uploadFile` reproduces that same canonical error shape, a post-rename upload failing because the parent folder is missing would have been mis-routed into the "doesn't exist, create it" branch and double-fired `uploadFileInDropbox`. Narrow the try/catch to wrap only `filesGetMetadata` and lift the folder-exists / file-rename / create-new branches into flat conditional flow on the captured metadata result. - Tighten `isCopilotApiError`: also require `url: string`. Pairing it with the existing `statusText` + `body` checks pins the guard to the SDK's `ApiError` shape (`url`, `status`, `statusText`, `body`, `request`) so it can't accidentally match unrelated Error subclasses that happen to carry `status` (DropboxResponseError, our own APIError, future SDK wrappers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
DropboxClient._downloadFile/_uploadFileand forward Dropbox's actualerror_summary/errorshape, so 409s show the real reason (path/not_found,path/restricted_content, etc.) in Sentry instead of the opaque "Response failed with a 4xx code"node_modules/copilot-node-sdk/dist/codegenimport with a structuralisCopilotApiErrorguard colocated inCopilotAPI.tserror.error.error.path['.tag']access increateAndUploadFileInDropbox's catch (was a latent TypeError when 409s with non-matching body shapes propagated)No behavior change for callers, retries, or downstream tasks. Orphan
fileFolderSyncrows continue flowing through the existing resync path.Linear: OUT-3598
Sentry: DROPBOX-INTEGRATION-17
Test plan
pnpm typecheckpassespnpm lintpasseserror_summary(e.g.path/not_found/...) in the event payloadFolder already existsretry path increateAndUploadFileToAssemblystill triggers (existing behavior, just swapped frominstanceofto structural guard)🤖 Generated with Claude Code