Skip to content

feat(sftp): bundle folder uploads and improve cancel/delete operations - #116

Merged
binaricat merged 5 commits into
mainfrom
feature/bundled-folder-uploads-and-fast-delete
Jan 21, 2026
Merged

feat(sftp): bundle folder uploads and improve cancel/delete operations#116
binaricat merged 5 commits into
mainfrom
feature/bundled-folder-uploads-and-fast-delete

Conversation

@binaricat

Copy link
Copy Markdown
Owner

Summary

  • Bundle folder uploads as single tasks - When uploading a folder from computer, show it as one aggregated task with total progress instead of individual files
  • Fix cancel upload - Properly cancel external uploads by calling the correct cancel function and using unique file transfer IDs for backend tracking
  • Fast folder deletion - Use SSH exec with rm -rf command for remote folder deletion instead of slow recursive SFTP rmdir
  • UI improvements - Add FolderUp icon for folder upload tasks, add cancelled status toast message

Changes

Bundle folder uploads

  • Added detectRootFolders helper to group entries by root folder
  • Create single bundled task per folder with aggregate byte count
  • Track progress across all files in the bundle

Fix cancel upload

  • Each file now uses unique fileTransferId for backend cancellation tracking
  • Added activeFileTransferIdsRef to track all active uploads
  • Modified cancelExternalUpload to cancel all active file uploads
  • Backend now checks uploadState.cancelled flag instead of just error message
  • Frontend catch block checks cancelUploadRef.current to break out of loop

Fast folder deletion

  • Added execSshCommand helper function in sftpBridge.cjs
  • Uses client.client (underlying ssh2 Client) to execute rm -rf command
  • Falls back to SFTP rmdir if SSH exec fails

Test plan

  • Drag a folder from computer to SFTP pane - should show as single task with aggregate progress
  • Click cancel button during folder upload - should stop immediately without errors
  • Delete a large folder on remote server - should complete quickly using rm -rf

🤖 Generated with Claude Code

- Bundle folder uploads as single tasks showing aggregate progress
- Add unique file transfer IDs for proper cancellation tracking
- Fix cancel button to call cancelExternalUpload for external uploads
- Improve backend cancel detection using cancelled flag instead of error message
- Use SSH exec with rm -rf for fast folder deletion on remote servers
- Add FolderUp icon for folder upload tasks in transfer queue
- Add i18n key for upload cancelled message

Co-Authored-By: Claude <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70a172216a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread application/state/sftp/useSftpExternalOperations.ts Outdated
Refactors the SFTP upload mechanism to provide a more unified and robust experience.

- **Bundles folder uploads**: When uploading a folder from the local machine, it now appears as a single, aggregated task in the UI, showing overall progress instead of individual files.
- **Enhances cancellation**: Implements a new upload service and controller to manage transfers, allowing for more immediate and reliable cancellation of both individual files and bundled folder uploads.
- **Improves UI feedback**: Adds dedicated buttons for cancelling active uploads and dismissing completed, failed, or cancelled tasks.
- **Faster folder deletion**: Utilizes SSH `rm -rf` command for rapid remote folder deletion, falling back to SFTP rmdir if SSH exec is unavailable.
- Updates internationalization keys for single item deletion confirmation.
Adopts a "fail-fast" strategy for SFTP uploads, stopping the entire transfer process upon encountering any error during file or directory operations.

Introduces a new `isFatalUploadError` helper to consolidate and expand the definition of errors that should halt an upload, now explicitly including cases where the target directory is deleted or inaccessible during the transfer.

Removes specific fatal error checks from various components, streamlining error propagation and simplifying the overall error handling logic.

Ensures "Scanning files..." placeholder tasks are consistently removed when actual upload tasks are added, preventing potential state inconsistencies.
Disables render tracking by default, making it an opt-in debugging feature. This change significantly reduces development log noise by only logging render information when explicitly enabled via a debug flag.

Also, explicitly sets `aria-describedby={undefined}` on dialog content. This ensures proper accessibility semantics when a description is not provided, preventing potential issues or warnings.
@binaricat

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6196c6e3c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/uploadService.ts
Comment thread components/sftp-modal/hooks/useSftpModalTransfers.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6196c6e3c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/uploadService.ts
Comment thread components/sftp-modal/hooks/useSftpModalTransfers.ts Outdated
- uploadService.ts: Mark all bundle tasks as cancelled on early loop exit
- uploadService.ts: Don't set wasCancelled for actual errors, preserving
  the error result for proper UI feedback
- useSftpModalTransfers.ts: Re-throw real errors instead of masking them
  as cancellations, only return cancelled:true for user-initiated cancels

This ensures genuine failures (permission denied, disk full, connection
loss) are reported as errors with proper toast messages, while user
cancellations are correctly identified and handled.

Co-Authored-By: Claude <noreply@anthropic.com>
@binaricat

Copy link
Copy Markdown
Owner Author

Addressed Review Feedback

Fixed the issues raised in the code review in commit c1959ad:

1. Mark bundle task cancelled on early loop exit

When cancelUploadRef (now handled via UploadController.isCancelled()) is detected between files, the code now marks all incomplete bundle tasks as cancelled before breaking:

if (controller?.isCancelled()) {
  wasCancelled = true;
  // Mark all created tasks as cancelled before breaking
  for (const [, bundleTaskId] of bundleTaskIds) {
    const progress = bundleProgress.get(bundleTaskId);
    if (progress && progress.completedCount < progress.fileCount) {
      callbacks?.onTaskCancelled?.(bundleTaskId);
    }
  }
  break;
}

2. Treat upload failures as failures, not cancellations

Removed wasCancelled = true from the error catch block in uploadService.ts. Now errors are properly reported via onTaskFailed callback and the result includes error field without the misleading cancelled: true.

3. Don't mask write errors as cancelled uploads

Updated useSftpModalTransfers.ts to only return cancelled: true when the transfer was actually user-cancelled. Real errors are now re-thrown to trigger proper error handling:

} catch (error) {
  const wasCancelled = cancelledTransferIdsRef.current.has(taskId);
  if (wasCancelled) {
    cancelledTransferIdsRef.current.delete(taskId);
    return { success: false, cancelled: true };
  }
  // Real error - propagate it by re-throwing
  throw error;
}

This ensures genuine failures (permission denied, disk full, connection loss) are reported with proper error messages in the UI.

@binaricat
binaricat merged commit 5bdd187 into main Jan 21, 2026
@binaricat
binaricat deleted the feature/bundled-folder-uploads-and-fast-delete branch January 22, 2026 01:09
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