{"record":{"id":"fff60052a8afae57","repo":"can1357/oh-my-pi","slug":"unable-to-infer-language-from-file-extension","errorCode":null,"errorMessage":"Unable to infer language from file extension: {}. Specify `lang` explicitly.","messagePattern":"Unable to infer language from file extension: (.+?)\\. Specify `lang` explicitly\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/pi-ast/src/ops.rs","lineNumber":82,"sourceCode":"}\n\n#[must_use]\npub fn supported_lang_list() -> String {\n\tSupportLang::sorted_aliases().join(\", \")\n}\n\npub fn resolve_supported_lang(value: &str) -> Result<SupportLang> {\n\tSupportLang::from_alias(value).ok_or_else(|| {\n\t\tanyhow!(\"Unsupported language '{value}'. Supported: {}\", supported_lang_list())\n\t})\n}\n\npub fn resolve_language(lang: Option<&str>, file_path: &Path) -> Result<SupportLang> {\n\tif let Some(lang) = lang.map(str::trim).filter(|lang| !lang.is_empty()) {\n\t\treturn resolve_supported_lang(lang);\n\t}\n\tSupportLang::from_path(file_path).ok_or_else(|| {\n\t\tanyhow!(\n\t\t\t\"Unable to infer language from file extension: {}. Specify `lang` explicitly.\",\n\t\t\tfile_path.display()\n\t\t)\n\t})\n}\n\n#[must_use]\npub fn is_supported_file(file_path: &Path, explicit_lang: Option<&str>) -> bool {\n\tif explicit_lang.is_some() {\n\t\treturn true;\n\t}\n\tresolve_language(None, file_path).is_ok()\n}\n\npub fn compile_pattern(\n\tpattern: &str,\n\tselector: Option<&str>,\n\tstrictness: &MatchStrictness,","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-ast/src/ops.rs#L64-L100","documentation":"When a set_todos() seed is a dict (phase seed), the client requires a 'name' key holding a non-empty, non-whitespace string. A missing, empty, or whitespace-only name means the phase cannot be identified or displayed, so RpcError is raised during normalization before sending.","triggerScenarios":"Passing a phase dict like {\"tasks\": [...]} with no 'name', or {\"name\": \"\"} or {\"name\": \"   \"} to set_todos().","commonSituations":"Programmatically building phases where name was conditionally omitted, deserializing phases from JSON/DB where an empty name was stored, or confusing flat todo items (which don't need name) with phase seeds (which do).","solutions":["Add a non-empty 'name' string to every phase seed dict.","Strip/validate names before calling set_todos(); drop or rename phases with empty names.","If you meant flat todo items, pass plain item dicts instead of phase-shaped dicts (keys like 'name' make it look like a phase).","Catch RpcError during normalization and report which phase index lacks a name."],"exampleFix":"// before\nclient.set_todos([{\"id\": \"p1\", \"tasks\": [{\"title\": \"do it\"}]}])\n// after\nclient.set_todos([{\"id\": \"p1\", \"name\": \"Setup\", \"tasks\": [{\"title\": \"do it\"}]}])","handlingStrategy":"validation","validationCode":"for i, seed in enumerate(phases):\n    name = seed.get(\"name\") if isinstance(seed, dict) else None\n    if not isinstance(name, str) or not name.strip():\n        raise ValueError(f\"phase {i + 1} needs a non-empty 'name'\")","typeGuard":"def is_valid_phase_seed(seed: object) -> TypeGuard[dict]:\n    name = seed.get(\"name\") if isinstance(seed, dict) else None\n    return isinstance(name, str) and bool(name.strip())","tryCatchPattern":"try:\n    client.set_todos(seeds)\nexcept RpcError as exc:\n    if \"non-empty 'name'\" in str(exc):\n        raise ValueError(f\"invalid phase in set_todos: {exc}\") from exc\n    raise","preventionTips":["Build phases through a constructor/helper that requires name as a parameter.","Strip and validate names when loading phases from JSON or a database.","Keep flat items and phases in separate code paths so phase-shaped keys aren't accidentally dropped."],"tags":["validation","rpc","todos","phases"],"backgroundTag":"missing-required-field","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}