Repository navigation
[Feature]: A transfer queue behind the upload panel — retry, pause/resume, and a conflict policy #24735
Description
Problem or use case
Short summary: an upload is a one-shot attempt. If it fails — a dropped connection is the usual way — the transfer is simply gone: no record of what was being sent, no way to retry it, and the only recovery is to find the files again and re-drag them. (S)FTP clients solved this thirty years ago with a queue: items wait, run, pause, resume, fail visibly, and can be retried without being re-selected.
What the code does today, per source file, is { status: 'imported' | 'skipped' | 'failed', reason } from a single attempt (src/main/ipc/filesystem-runtime-upload-staging.ts:40-90). Nothing retries; nothing remembers. The SSH path is explicit about where that leaves the user — when the session is gone it throws "Remote file upload is unavailable. Reconnect the SSH target and retry." (src/main/providers/ssh-filesystem-file-upload.ts:25). The retry it asks for is entirely manual, and the list of what was in flight no longer exists anywhere.
Two things make this the right moment to ask:
- feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107 adds a per-file progress panel with a cancel control per row, scoped to the workspace the files are going to, and feat(transfers): progress and cancel for SSH uploads and all remote downloads #23849 extends the same panel to SSH uploads and all remote downloads. Between them the surface this issue needs is already designed and in review. What they do not add is a queue underneath it: cancel removes an item, nothing re-enqueues one.
- [Bug]: Drop upload failures report a count, never the reason #21219 is about the other half of a failure — the reason is produced and then dropped on the floor. A queue is the natural place to put it: a failed row that stays in a list has somewhere to show why.
And one behaviour nobody can currently change: collisions are never overwritten and never asked about. The import path picks a non-colliding name through deconflictName and reports renamed (src/main/ipc/filesystem-import-ssh.ts:160-187), while the transport opens the remote file with 'wx' because callers always pass exclusive: true (src/main/ssh/sftp-upload.ts:37-39, 73-74). Re-uploading a corrected file therefore produces a second copy beside the stale one, and the only way to replace the original is to delete it by hand first.
Proposed solution
A queue as the model behind the panel from #16107 / #23849 — not a second surface.
- Items with states.
queued → running → done, pluspaused,failedandskipped. An item carries source, destination workspace and path, size, bytes moved, attempt count, and the failure reason when it has one. Items survive the failure that ended their transfer — that is the whole point — and ideally an app restart, since a dropped VPN and a closed laptop look the same to the queue. - Retry. Per item and Retry all failed. For transport-level failures (connection lost, session replaced) retry automatically on reconnect with a bounded backoff, because the user did nothing wrong and re-dragging is pure ceremony. For policy failures (too large, permission denied, unsupported type) do not auto-retry — show the reason and let the user decide.
- Pause and resume, distinct from the cancel that feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107 already implements. Cancel means undo this transfer and roll back what it created; pause means stop moving bytes, keep the item. The SFTP layer already has the primitives for a real resume: an abort signal per transfer, and an append flag that opens the remote file with
'a'(sftp-upload.ts:123-128), so a resumed item can continue at an offset rather than start over — worth having for the 2 GB case, and acceptable to re-start smaller files in a first version. - A conflict policy, including Overwrite All. When a destination name exists, offer Overwrite, Overwrite all, Skip, Skip all, and Keep both — with Keep both as the default so today's behaviour is preserved for anyone who does not care. The choice applies to the rest of the batch, which is the part that matters when 40 files land on 40 existing ones. At the transport level this is already one flag:
exclusive: falseopens with'w'instead of'wx'. - Order and concurrency. A small number of parallel transfers, reorderable, so one large file does not hold up three small ones; and a visible queue position, which is the thing an FTP client gives you that a spinner never does.
- Status at a glance. A per-row state icon (queued, running, paused, failed, done), the failure reason inline on a failed row ([Bug]: Drop upload failures report a count, never the reason #21219), and an aggregate indicator that survives switching away — docked at the bottom of the file explorer when the explorer is where the transfer came from, which is where the eye already is after a drop.
Sequencing: this should land after #16107 and #23849 rather than compete with them. They build the panel and the per-transfer plumbing (progress events, cancel, rollback ledger); this turns the list they render into a queue that outlives a failure.
Alternatives or additional context
- Re-dragging the files is the current workaround. It loses the destination, the selection and any partial progress, and for a large directory import it means starting a multi-minute transfer from zero.
- A retry button on a failed row, and nothing else, would cover most of the pain for a fraction of the work. If the full queue is too much at once, that is the cut line I would choose: retry first, pause/resume and the conflict policy after.
- Auto-retry without a queue would be worse than nothing: without a visible list, a silent second attempt that also fails is indistinguishable from an upload that never happened.
- Related: [Feature]: Support uploading local files in file explorer during remote SSH sessions #18644 (uploading local files from the file explorer during remote SSH sessions), [Feature]: Reuse the uploaded copy when the same file is dropped onto a remote terminal again #23842 (reuse an already-uploaded copy instead of sending it twice), [Feature]: Show progress while uploading dropped files to a remote workspace #16077 and [Feature]: Show drop upload progress only in the workspace the files are going to #24151 (progress reporting and its scope, both addressed by feat(runtime): per-file progress and cancel for files dropped onto a remote workspace #16107).
Written with Claude Code (Opus 5); file and line references are from main today.
Activity