{"record":{"id":"e6f91f83a8c5e706","repo":"spacedriveapp/spacedrive","slug":"failed-to-build-action","errorCode":null,"errorMessage":"Failed to build action: {}","messagePattern":"Failed to build action: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"apps/cli/src/domains/file/mod.rs","lineNumber":120,"sourceCode":"\t\t\t\t}\n\t\t\t);\n\t\t}\n\t}\n\tOk(())\n}\n\n/// Run file copy with confirmation handling\nasync fn run_copy_with_confirmation(\n\tctx: &Context,\n\tmut input: sd_core::ops::files::copy::input::FileCopyInput,\n) -> Result<JobId> {\n\tuse crate::util::confirm::prompt_for_choice;\n\tuse sd_core::infra::action::LibraryAction;\n\tuse sd_core::ops::files::copy::action::FileCopyAction;\n\n\t// Build the action from input for validation purposes\n\tlet action = FileCopyAction::from_input(input.clone())\n\t\t.map_err(|e| anyhow::anyhow!(\"Failed to build action: {}\", e))?;\n\n\t// Use the action's validation method to check for conflicts\n\t// For CLI validation, we'll use a simplified approach since we don't have full library context\n\t// In a production system, you'd want to pass the actual library context\n\n\t// Simple conflict detection - check if destination exists and overwrite is not enabled\n\tif !input.overwrite {\n\t\tlet has_conflict = check_for_simple_conflicts(&action).await?;\n\t\tif has_conflict {\n\t\t\tuse sd_core::infra::action::ConfirmationRequest;\n\n\t\t\tlet request = ConfirmationRequest {\n\t\t\t\tmessage: \"Destination file(s) already exist. What would you like to do?\"\n\t\t\t\t\t.to_string(),\n\t\t\t\tchoices: vec![\n\t\t\t\t\t\"Overwrite the existing file(s)\".to_string(),\n\t\t\t\t\t\"Rename the new file(s) (e.g., file.txt -> file (1).txt)\".to_string(),\n\t\t\t\t\t\"Abort this copy operation\".to_string(),","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/spacedriveapp/spacedrive/blob/6dfeccf2113039e35f2ce735f945e70dc3e4ea45/apps/cli/src/domains/file/mod.rs#L102-L138","documentation":"Before executing a copy, the CLI rebuilds the action via `FileCopyAction::from_input(input)`, which delegates to the core builder's `build()` (core/src/ops/files/copy/action.rs:279) and fails on invalid builder state. The CLI wraps that failure string in 'Failed to build action: {}'. The text after the colon is the underlying builder error and names the real problem, e.g. missing or empty sources/destination.","triggerScenarios":"Running `sd-cli file copy` where argument parsing produced an empty source list or an unset destination (e.g. flags consumed by a shell glob that matched nothing), or a FileCopyInput assembled programmatically with missing required fields.","commonSituations":"Scripts passing an unquoted glob that expands to zero files; copying where the source argument was eaten by an earlier flag; version drift between the CLI's input shape and the core builder's required fields.","solutions":["Read the suffix after 'Failed to build action: ' — it is the exact builder message (which field was missing).","Verify the arguments actually arrived: quote globs (`sd-cli file copy '/data/*.txt'` style handling) and echo the expanded command before running.","Confirm the source path exists and the destination is provided on the command line.","If the inner message mentions a field mismatch, your sd-cli build may be older than core — rebuild the workspace (`cargo build`) so CLI and core agree."],"exampleFix":"# before\ncd /data && sd-cli file copy *.txt /backup  # glob matched nothing -> empty sources -> Failed to build action: ...\n\n# after\nls /data/*.txt >/dev/null && sd-cli file copy /data/*.txt /backup","handlingStrategy":"validation","validationCode":"fn copy_input_valid(input: &FileCopyInput) -> bool {\n    !input.sources.is_empty() && input.destination.as_os_str().len() > 0\n}\nif !copy_input_valid(&input) { anyhow::bail!(\"copy needs at least one source and a destination\"); }","typeGuard":null,"tryCatchPattern":"if let Err(e) = run_copy_with_confirmation(ctx, input).await {\n    let msg = e.to_string();\n    if msg.contains(\"Failed to build action\") {\n        // the suffix after the colon is the builder's own message; surface only that\n        eprintln!(\"copy rejected: {}\", msg.split_once(\": \").map(|(_, r)| r).unwrap_or(&msg));\n    }\n    return Err(e);\n}","preventionTips":["Quote globs and confirm they match something before passing them as copy sources.","Validate that source and destination arguments are present before invoking the CLI from scripts.","Keep sd-cli and core built from the same commit so the input struct and builder stay in sync."],"tags":["cli","file-copy","action-builder","validation"],"backgroundTag":null,"analyzedSha":"6dfeccf2113039e35f2ce735f945e70dc3e4ea45","analyzedAt":"2026-08-16T11:26:17.074Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}