{"record":{"id":"8571dca2bc29c41b","repo":"affaan-m/ECC","slug":"legacy-workspace-source-must-be-a-directory","errorCode":null,"errorMessage":"Legacy workspace source must be a directory: {}","messagePattern":"Legacy workspace source must be a directory: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"ecc2/src/main.rs","lineNumber":6018,"sourceCode":"        parts.push(format!(\"--project {}\", shell_quote_double(project)));\n    }\n    if let Some(task_group) = draft.task_group.as_deref() {\n        parts.push(format!(\"--task-group {}\", shell_quote_double(task_group)));\n    }\n    Some(parts.join(\" \"))\n}\n\nfn import_legacy_schedules(\n    db: &session::store::StateStore,\n    cfg: &config::Config,\n    source: &Path,\n    dry_run: bool,\n) -> Result<LegacyScheduleImportReport> {\n    let source = source\n        .canonicalize()\n        .with_context(|| format!(\"Legacy workspace not found: {}\", source.display()))?;\n    if !source.is_dir() {\n        anyhow::bail!(\n            \"Legacy workspace source must be a directory: {}\",\n            source.display()\n        );\n    }\n\n    let drafts = load_legacy_schedule_drafts(&source)?;\n    let source_path = source.join(\"cron/jobs.json\");\n    let source_path = source_path\n        .strip_prefix(&source)\n        .unwrap_or(&source_path)\n        .display()\n        .to_string();\n\n    let mut report = LegacyScheduleImportReport {\n        source: source.display().to_string(),\n        source_path,\n        dry_run,\n        jobs_detected: drafts.len(),","sourceCodeStart":6000,"sourceCodeEnd":6036,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/ecc2/src/main.rs#L6000-L6036","documentation":"The legacy schedule import canonicalizes the source workspace path and then explicitly checks `is_dir()`. If the canonicalized path resolves to a file rather than a directory, it bails with this message including the path. The importer walks the directory tree for schedule drafts, so a directory is required.","triggerScenarios":"Passing a file path (e.g. a single jobs JSON file or an archive) to the legacy schedule import where a workspace directory is expected; a symlink resolving to a file after canonicalization.","commonSituations":"Confusing the schedule-import command with the jobs-file option (file) vs the workspace argument (directory); pointing at `config.json` inside the legacy workspace instead of the workspace root; a broken symlink chain that canonicalizes to a file.","solutions":["Pass the legacy workspace root directory, not a file, as the source argument.","Verify with `ls -d <path>` that the target is a directory after resolving symlinks.","If you intended to import a single jobs file, use the jobs-file option/command instead of the workspace import."],"exampleFix":"// before\nimport_legacy_schedules(Path::new(\"~/old-ws/jobs.json\"))?;\n\n// after\nimport_legacy_schedules(Path::new(\"~/old-ws\"))?;","handlingStrategy":"validation","validationCode":"// Rust\nlet source = Path::new(raw);\nif !source.is_dir() {\n    anyhow::bail!(\"source must be a directory: {}\", source.display());\n}","typeGuard":"fn is_directory(p: &Path) -> bool {\n    p.is_dir()\n}","tryCatchPattern":"match import_legacy_schedules(&source) {\n    Err(e) if e.to_string().contains(\"must be a directory\") => {\n        eprintln!(\"Pass the workspace root directory, not a file: {}\", source.display());\n    }\n    other => other?,\n}","preventionTips":["Keep file-based imports (single jobs file) and directory-based imports on separate, clearly named commands/flags.","Resolve symlinks before passing paths to migration commands.","Document that workspace arguments must be directories in the command help text."],"tags":["filesystem","migration","rust"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}