Skip to content

fix(dashboard): await upload status before parsing the resume response - #10534

Open
dafyy321-pixel wants to merge 1 commit into
AstrBotDevs:masterfrom
dafyy321-pixel:fix/chunked-upload-resume
Open

dafyy321-pixel wants to merge 1 commit into
AstrBotDevs:masterfrom
dafyy321-pixel:fix/chunked-upload-resume

Conversation

@dafyy321-pixel

@dafyy321-pixel dafyy321-pixel commented Oct 12, 2026 •

Copy link
Copy Markdown

Fixes #10533.

This makes resume() await the upload-status request before parsing the response. A valid session now keeps its upload ID and sends only the chunks the server has not received.

Currently, a Promise is passed into the synchronous envelopeData() helper. The outer await does not delay that helper call. The parser sees no response envelope, and the existing catch block clears uploadId as though the session were expired. The uploader then initializes another session and sends every chunk again, even if the original status request returns successfully.

The production change is one line in the shared scheduler. It applies to both the backup dialog and chunked WebChat attachments.

Modifications

Await the status response at the point it is consumed

In dashboard/src/composables/useChunkedUpload.ts, change:

const data = await envelopeData(api.statusUpload({ upload_id: uploadId }));

to:

const data = envelopeData(await api.statusUpload({ upload_id: uploadId }));

statusUpload() is asynchronous; envelopeData() is synchronous. This order matches the composable's existing initialization, chunk-upload, and completion calls, which also await the endpoint response before parsing it.

With the resolved envelope available, received_chunks feeds the existing progress calculation and missing-chunk filter. No new scheduling algorithm or state variable is needed.

The same try/catch still handles an expired response or a rejected status request. In those cases, the existing fallback clears the upload ID, creates a fresh session, and uploads the full file. A rejected Promise is now awaited inside that handler rather than being passed to a parser that cannot observe its result.

Exercise the actual composable in four resume scenarios

dashboard/tests/chunkedUploadResume.test.mjs imports the TypeScript source through the existing TypeScript/Vue dependencies and runs the actual composable. It uses Node's test runner and a real File; the Vue refs and computed progress are part of the behavior under test.

The fixture uses a 14-byte file divided into chunks of 4, 4, 4, and 2 bytes. It first leaves the uploader in a resumable state after the first 12 bytes have been accepted, then supplies each of the status scenarios below.

Resume scenario Expected session behavior Expected uploads after resume
Valid session; chunks 0, 1, 2 received Retain session-1 Only chunk 3, with a 2-byte payload
Valid session; all four chunks received Retain session-1 No chunks; complete the existing session
Expired-session response envelope Initialize session-2 Upload chunks 0, 1, 2, 3
Status request rejects Initialize session-2 Upload chunks 0, 1, 2, 3

Each case checks the queried session ID, number of initialized sessions, uploaded chunk indices, upload destination, and completed session. The missing-chunk case also verifies that progress is restored to 12 / 14 bytes and 86% before the final chunk is uploaded.

Completion verifies 14 / 14 bytes and 100%. The final assertions check that the attachment result is returned, status becomes done, the error message is cleared, and canResume becomes false. The all-received case guards the useful edge where an interrupted client needs only to finalize the existing server-side transfer.

Request sequence after the change

For a valid original session with one missing chunk:

Original transfer:
  initUpload -> session-1
  server receives chunks 0, 1, 2

Resume:
  statusUpload(session-1) -> received_chunks: [0, 1, 2]
  uploadChunk(session-1, 3) -> 2 bytes
  completeUpload(session-1)

There is one initialization across the original transfer and its resume. Previously received chunks do not consume another upload request, and progress reflects the server's existing data as soon as status is available.

For a session that genuinely needs replacement, the fallback still initializes a second session and completes a full upload. The four cases cover both paths rather than changing the valid-session path at the expense of recovery behavior.

Verification Steps and Test Results

Validated locally on Windows using Node.js 24.19.0, pnpm 10.28.2, and Ruff 0.15.22.

Run from dashboard/:

node --test tests/chunkedUploadResume.test.mjs
4 passed

node --test tests/*.test.mjs
67 passed

node scripts/check-i18n-keys.mjs
Translation-key check passed

npx --yes pnpm@10.28.2 run build
Type checking and production build completed successfully

The focused resume cases pass 4/4, and the dashboard Node tests pass 67/67. The production build includes vue-tsc --noEmit and Vite; both completed successfully, with 5,668 modules transformed during bundling. The i18n check passed across four languages, each containing 4,130 keys, and checked 121 source files.

Full-repository Ruff formatting and static analysis also passed. git diff --check is clean.

Compatibility and review scope

Only the composable's status-parser line and the new regression file change. The tests use dependencies already in the dashboard; there are no package-manifest or lockfile changes.

The five upload API methods, request/response schemas, upload limits, concurrency limit, per-chunk retry count, cancellation handling, and server-side session lifetime remain as implemented. The change restores the existing same-page resume behavior while the composable still holds the file and session. It does not add persistence across a page reload.

No button labels, navigation, or page structure change, so the existing WebUI usage instructions and screenshots remain applicable. The backend upload routes and generated API client also remain the same.

This is separate from #10201's start/resume guards and #10427's backend assembly work. It corrects the order in which an existing frontend request is awaited and parsed.

Checklist

  • This is a bug fix with no new feature proposal.
  • This is not a breaking change; the existing upload API and state contract remain the same.
  • Verification steps and passing results are included above.
  • Checked the WebUI documentation requirement; no navigation, page structure, or labels change.
  • No new dependencies, generated-client changes, or lockfile changes.
  • The changes are limited to awaiting the resume-status response and its regression coverage.

Summary by Sourcery

Fix chunked-upload resume processing and verify both session reuse and recovery paths.

Bug Fixes:

  • Fix resume handling so upload status responses are awaited before parsing, preserving valid sessions and uploading only missing chunks.
  • Retain the existing fallback behavior for expired sessions or rejected status requests by starting a fresh upload.

Tests:

  • Add regression coverage for resumed uploads with missing chunks, fully received chunks, expired sessions, and rejected status requests.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. If the awaited status response is interpreted incorrectly, resume could upload the wrong set of chunks or complete an incorrect upload, leaving bounded server-side upload data or an attachment behind. Reverting would stop the behavior but would not remove any chunks or completed attachment already written; those would need cleanup or a retry.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

This branch has not been deployed

No deployments
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.

[Bug] Resuming a chunked upload starts a new session and retransmits received chunks

1 participant