{"record":{"id":"d402fbd7505f2feb","repo":"nexu-io/open-design","slug":"configured-uploadpath-for-participant-participant","errorCode":null,"errorMessage":"Configured uploadPath for participant {participant['name']} does not exist: {source}. Fix the upload avatar path before preparing the bundle.","messagePattern":"Configured uploadPath for participant (.+?) does not exist: (.+?)\\. Fix the upload avatar path before preparing the bundle\\.","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/chat-motion-overlay/scripts/prepare_chat_overlay_bundle.py","lineNumber":96,"sourceCode":"def write_chat_spec_ts(spec: dict, output_dir: Path) -> None:\n    target = output_dir / \"src\" / \"chatSpec.ts\"\n    content = \"export const chatSpec = \" + json.dumps(spec, ensure_ascii=False, indent=2) + \" as const;\\n\"\n    target.write_text(content, encoding=\"utf-8\")\n\n\ndef remove_local_upload_paths(spec: dict) -> None:\n    for participant in spec[\"participants\"]:\n        participant.pop(\"uploadPath\", None)\n\n\ndef copy_avatar_assets(spec: dict, output_dir: Path) -> dict:\n    upload_sources: list[tuple[dict, Path]] = []\n    for participant in spec[\"participants\"]:\n        upload_path = participant.get(\"uploadPath\")\n        if upload_path:\n            source = Path(upload_path).expanduser().resolve()\n            if not source.exists():\n                raise SystemExit(\n                    f\"Configured uploadPath for participant {participant['name']} does not exist: {source}. \"\n                    \"Fix the upload avatar path before preparing the bundle.\"\n                )\n            upload_sources.append((participant, source))\n    public_dir = output_dir / \"public\"\n    public_dir.mkdir(parents=True, exist_ok=True)\n    for preset_file in avatar_library_dir().glob(\"*.png\"):\n        shutil.copy2(preset_file, public_dir / preset_file.name)\n    for participant, source in upload_sources:\n        target_name = f\"{participant['id']}-upload{source.suffix.lower()}\"\n        shutil.copy2(source, public_dir / target_name)\n        participant[\"uploadAsset\"] = target_name\n    remove_local_upload_paths(spec)\n    return spec\n\n\ndef main() -> None:\n    args = parse_args()","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/chat-motion-overlay/scripts/prepare_chat_overlay_bundle.py#L78-L114","documentation":"Thrown by copy_avatar_assets() in prepare_chat_overlay_bundle.py when a participant's resolved uploadPath does not point at an existing file on disk. The script copies upload avatars into the bundle's public/ folder, so a missing source cannot be bundled. It fires during prepare, after the template has been copied but before chatSpec.ts is written; on failure the partial bundle is deleted.","triggerScenarios":"Run prepare_chat_overlay_bundle.py with avatarMode in {upload, mixed} and a participant uploadPath that does not exist (wrong path, typo, relative path resolved against the wrong cwd, or file deleted). The run_test_matrix.py case 'invalid_upload_missing_file' reproduces this.","commonSituations":"Relative uploadPath interpreted against a different cwd than expected; a path copied from another machine; the avatar file was moved or deleted after the config was written; ~ expansion differences.","solutions":["Verify the uploadPath exists with `ls -l <path>` from the same cwd the script runs in.","Use an absolute path (or a path starting with ~) for uploadPath to avoid cwd ambiguity; the script runs Path(...).expanduser().resolve().","If the avatar is gone, regenerate or pick a preset avatar and switch avatarMode accordingly."],"exampleFix":"// before\n{\"participants\": {\"老婆\": {\"uploadPath\": \"avatars/me.png\"}}}  // not found from skill cwd\n// after\n{\"participants\": {\"老婆\": {\"uploadPath\": \"~/projects/avatars/me.png\"}}}","handlingStrategy":"validation","validationCode":"from pathlib import Path\ndef assert_uploads_exist(spec: dict) -> None:\n    missing = []\n    for p in spec.get(\"participants\", []):\n        up = p.get(\"uploadPath\")\n        if up and not Path(up).expanduser().resolve().exists():\n            missing.append((p[\"name\"], up))\n    if missing:\n        raise SystemExit(f\"upload avatars missing: {missing}\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use absolute or ~-prefixed paths for uploadPath to avoid cwd ambiguity.","Verify avatar files exist immediately before running prepare_chat_overlay_bundle.py."],"tags":["filesystem-safety","chat-motion-overlay","bundle-prep","avatar-mode"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}