{"record":{"id":"cc9281abec52eaa6","repo":"spacedriveapp/spacedrive","slug":"operation-cancelled","errorCode":null,"errorMessage":"Operation cancelled","messagePattern":"Operation cancelled","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"info","filePath":"core/src/ops/files/copy/strategy.rs","lineNumber":903,"sourceCode":"\tlet mut last_progress_update = std::time::Instant::now();\n\n\tlet mut source_hasher = if verify_checksum {\n\t\tSome(blake3::Hasher::new())\n\t} else {\n\t\tNone\n\t};\n\n\tlet mut dest_hasher = if verify_checksum {\n\t\tSome(blake3::Hasher::new())\n\t} else {\n\t\tNone\n\t};\n\n\tloop {\n\t\tif let Err(_) = ctx.check_interrupt().await {\n\t\t\t// Clean up partial file so resume doesn't see corrupted data.\n\t\t\tlet _ = fs::remove_file(destination).await;\n\t\t\treturn Err(std::io::Error::new(\n\t\t\t\tstd::io::ErrorKind::Interrupted,\n\t\t\t\t\"Operation cancelled\",\n\t\t\t));\n\t\t}\n\n\t\tlet bytes_read = source_file.read(&mut buffer).await?;\n\t\tif bytes_read == 0 {\n\t\t\tbreak;\n\t\t}\n\n\t\tlet chunk = &buffer[..bytes_read];\n\t\tdest_file.write_all(chunk).await?;\n\t\ttotal_copied += bytes_read as u64;\n\n\t\tif let Some(hasher) = &mut source_hasher {\n\t\t\thasher.update(chunk);\n\t\t}\n\t\tif let Some(hasher) = &mut dest_hasher {","sourceCodeStart":885,"sourceCodeEnd":921,"githubUrl":"https://github.com/spacedriveapp/spacedrive/blob/6dfeccf2113039e35f2ce735f945e70dc3e4ea45/core/src/ops/files/copy/strategy.rs#L885-L921","documentation":"Returned by the single-file copy loop when ctx.check_interrupt() signals that the job was cancelled mid-transfer. Before returning, the code removes the partial destination file so a later resume never treats truncated data as complete, then returns io::Error with ErrorKind::Interrupted. This is an intentional cancellation path, not corruption or a bug.","triggerScenarios":"User cancels a copy job from the UI or CLI while bytes are streaming; the job manager aborts the job (shutdown, parent job failure, timeout policy) between chunk reads; daemon restart interrupts active copy jobs.","commonSituations":"Cancelling a large transfer partway; system shutdown racing an in-flight copy; a workflow's child copy job cancelled because a sibling step failed.","solutions":["Treat ErrorKind::Interrupted as a clean cancel: report 'cancelled' to the user, do not retry automatically","If the cancel was unintended, inspect job logs for who issued the interrupt (user action vs shutdown)","Re-run the copy job; the partial destination was already deleted, so the retry starts clean"],"exampleFix":"// before: any error is surfaced as a generic failure\nlet res = copy_with_strategy(...).await;\n\n// after: distinguish intentional cancellation\nmatch copy_with_strategy(...).await {\n    Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {\n        report.cancelled(); // user-initiated, not a defect\n    }\n    other => other?,\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_cancellation(err: &std::io::Error) -> bool {\n    err.kind() == std::io::ErrorKind::Interrupted\n}","tryCatchPattern":"match copy_result {\n    Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {\n        // clean cancel: partial file already removed by the strategy\n        state.mark_cancelled();\n    }\n    Err(e) => return Err(e),\n    Ok(_) => {}\n}","preventionTips":["Model cancellation as a first-class UI state, distinct from failure","Do not auto-retry Interrupted errors: that fights the user's intent","Remember the strategy deletes the partial destination, so retries always start clean"],"tags":["copy","cancellation","interrupted","jobs"],"backgroundTag":null,"analyzedSha":"6dfeccf2113039e35f2ce735f945e70dc3e4ea45","analyzedAt":"2026-08-16T11:26:17.074Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}