{"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":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"ecc2/src/main.rs","lineNumber":5132,"sourceCode":"            if observation.pinned { \"/pinned\" } else { \"\" },\n            observation.entity_name\n        );\n        if let Some(session_id) = observation.session_id.as_deref() {\n            line.push_str(&format!(\" | {}\", short_session(session_id)));\n        }\n        lines.push(line);\n        lines.push(format!(\"  summary {}\", observation.summary));\n    }\n\n    lines.join(\"\\n\")\n}\n\nfn build_legacy_migration_audit_report(source: &Path) -> Result<LegacyMigrationAuditReport> {\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 mut artifacts = Vec::new();\n\n    let scheduler_paths = collect_existing_relative_paths(\n        &source,\n        &[\"cron/scheduler.py\", \"jobs.py\", \"cron/jobs.json\"],\n    );\n    if !scheduler_paths.is_empty() {\n        artifacts.push(LegacyMigrationArtifact {\n            category: \"scheduler\".to_string(),\n            readiness: LegacyMigrationReadiness::ReadyNow,\n            detected_items: scheduler_paths.len(),\n            source_paths: scheduler_paths,\n            mapping: vec![","sourceCodeStart":5114,"sourceCodeEnd":5150,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/main.rs#L5114-L5150","documentation":"Thrown by build_legacy_migration_audit_report after it canonicalizes the --source path. The migration audit expects source to be the root directory of a legacy workspace; if the canonicalized path exists but is not a directory (it is a regular file, socket, or symlink to a file), the audit aborts because it walks subdirectories like cron/, workspace/, skills/.","triggerScenarios":"Invoking the legacy migration audit command with a --source that points at a file (e.g. passing 'workspace.tar' or 'cron/jobs.json' instead of the workspace root), or a path whose canonical target is a file rather than the workspace directory.","commonSituations":"User passes a path to a single artifact instead of the workspace root; a symlink in the path resolves to a file; the workspace was extracted from an archive into a file rather than a directory.","solutions":["Pass the legacy workspace root directory to --source, not a file inside it (e.g. './old-workspace' not './old-workspace/cron/jobs.json').","Verify with 'ls -ld <path>' that the target is a directory before running the command.","If the workspace is packaged as a tar/zip, extract it to a directory first and point --source at the extracted directory.","Resolve any symlinks in the path to confirm the final target is a directory."],"exampleFix":"# before\necc2 legacy audit --source ./old-workspace/cron/jobs.json\n\n# after\necc2 legacy audit --source ./old-workspace","handlingStrategy":"validation","validationCode":"use std::path::Path;\nfn ensure_workspace_dir(source: &Path) -> anyhow::Result<()> {\n    let canon = source.canonicalize()\n        .with_context(|| format!(\"workspace not found: {}\", source.display()))?;\n    if !canon.is_dir() {\n        anyhow::bail!(\"workspace source must be a directory: {}\", canon.display());\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always run 'ls -ld <source>' before passing it to the legacy audit command.","Standardize on passing the workspace root, never a file inside it."],"tags":["legacy-migration","filesystem","input-validation","cli"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}