Description
POST /remote/transfers/:id/cancel only flips the DB row and writes an audit event — nothing is sent to the device. The agent keeps transferring after a "successful" cancel, so server/UI state (failed, "Cancelled by user") diverges from device reality until the transfer runs to completion.
Root Cause
apps/api/src/routes/remote/transfers.ts:360-415 — the cancel route does db.update(fileTransfers).set({ status: 'failed', errorMessage: 'Cancelled by user', ... }) + logSessionAudit, and returns. No device command is queued, no WS/relay message is sent.
The agent side is fully built and waiting: CmdCancelTransfer = "cancel_transfer" (agent/internal/remote/tools/types.go:74), handler registered (agent/internal/heartbeat/handlers_desktop.go:135), and filetransfer.Manager.CancelTransfer correctly flags in-flight transfers (loop checks at transfer.go:166-171, 277-282). But grep -rn "cancel_transfer" apps/api/src finds only a docsIndex.json mention — zero senders.
Note for whoever fixes this: the transfer create route (transfers.ts:37) doesn't queue a file_transfer command either — trace how file_transfer commands actually reach the agent today (likely the remote-session relay / viewer path, remoteTools capability) and wire cancel through the same channel. Also apps/web/src/components/remote/FileManager.tsx:552 — cancelTransfer only removes the item from local React state and never calls the API cancel route at all.
Proposed Fix
- Make the API cancel route deliver
cancel_transfer to the device via the same channel file_transfer uses.
- Wire the web FileManager cancel button to the API route.
- Only mark the row cancelled/failed after (or alongside) dispatch, and consider a distinct
cancelled status vs failed.
Affected Files
apps/api/src/routes/remote/transfers.ts (primary)
apps/web/src/components/remote/FileManager.tsx
agent/internal/heartbeat/handlers_desktop.go (receiver — already works)
Found during the macOS agent memory audit follow-ups (PR #2391 review, #2388).
Description
POST /remote/transfers/:id/cancelonly flips the DB row and writes an audit event — nothing is sent to the device. The agent keeps transferring after a "successful" cancel, so server/UI state (failed, "Cancelled by user") diverges from device reality until the transfer runs to completion.Root Cause
apps/api/src/routes/remote/transfers.ts:360-415— the cancel route doesdb.update(fileTransfers).set({ status: 'failed', errorMessage: 'Cancelled by user', ... })+logSessionAudit, and returns. No device command is queued, no WS/relay message is sent.The agent side is fully built and waiting:
CmdCancelTransfer = "cancel_transfer"(agent/internal/remote/tools/types.go:74), handler registered (agent/internal/heartbeat/handlers_desktop.go:135), andfiletransfer.Manager.CancelTransfercorrectly flags in-flight transfers (loop checks attransfer.go:166-171,277-282). Butgrep -rn "cancel_transfer" apps/api/srcfinds only adocsIndex.jsonmention — zero senders.Note for whoever fixes this: the transfer create route (
transfers.ts:37) doesn't queue afile_transfercommand either — trace howfile_transfercommands actually reach the agent today (likely the remote-session relay / viewer path,remoteToolscapability) and wire cancel through the same channel. Alsoapps/web/src/components/remote/FileManager.tsx:552—cancelTransferonly removes the item from local React state and never calls the API cancel route at all.Proposed Fix
cancel_transferto the device via the same channelfile_transferuses.cancelledstatus vsfailed.Affected Files
apps/api/src/routes/remote/transfers.ts(primary)apps/web/src/components/remote/FileManager.tsxagent/internal/heartbeat/handlers_desktop.go(receiver — already works)Found during the macOS agent memory audit follow-ups (PR #2391 review, #2388).