{"record":{"id":"4713fecc79c29de9","repo":"zylon-ai/private-gpt","slug":"unknown-message-role-self-role-expected-system","errorCode":null,"errorMessage":"Unknown message role {self.role}. Expected 'system', 'user', 'assistant', or 'tool'.","messagePattern":"Unknown message role (.+?)\\. Expected 'system', 'user', 'assistant', or 'tool'\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"private_gpt/chat/input_models.py","lineNumber":875,"sourceCode":"                current_group.append(msg)\n\n        if current_group:\n            groups.append(current_group)\n\n        return groups\n\n    def _convert_into_llama_index_messages(\n        self,\n        tool_uses: dict[str, ToolUseBlock] | None = None,\n    ) -> tuple[list[\"ChatMessage\"], dict[str, ToolUseBlock]]:\n        tool_uses = tool_uses or {}\n\n        if self.role in [\"system\", \"user\"]:\n            return self._convert_message(), tool_uses\n        elif self.role == \"assistant\":\n            return self._convert_assistant_message(tool_uses)\n        else:\n            raise ValueError(\n                f\"Unknown message role {self.role}. Expected 'system', 'user', 'assistant', or 'tool'.\"\n            )\n\n    def _extract_content(\n        self,\n        content: str | BaseContentBlock | Sequence[ContentBlockType] | None,\n    ) -> tuple[list[LIContentBlock] | None, dict[str, list[ContentBlockType]] | None]:\n        \"\"\"Extract text content from various content types.\"\"\"\n        blocks: list[LIContentBlock] = []\n        custom_blocks: dict[str, list[ContentBlockType]] = {}\n        if isinstance(content, str):\n            blocks.append(LITextBlock(text=content))\n            content_blocks: Sequence[ContentBlockType] = []\n        elif isinstance(content, BaseContentBlock):\n            content_blocks = [cast(ContentBlockType, content)]\n        else:\n            content_blocks = content or []\n","sourceCodeStart":857,"sourceCodeEnd":893,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/chat/input_models.py#L857-L893","documentation":"Raised by POST /skills/{skill_id}/versions when the target skill does not exist in the specified collection. Version creation is only valid for an existing skill, so the pre-check on get_skill returning None yields 404.","triggerScenarios":"Posting a new version for a skill id that was deleted, belongs to another collection, or is misspelled; racing with a concurrent delete.","commonSituations":"Uploading a new version from a stale edit screen after the skill was removed elsewhere; collection switch in the UI not propagated to the version form.","solutions":["GET the skill first to confirm it exists in the collection, then post the version.","If 404, recreate the skill (POST /skills) instead of adding a version.","Refresh skill state in the editor before the version upload."],"exampleFix":"// before\nawait skillsApi.createVersion(id, collection, files);\n\n// after\nconst existing = await skillsApi.get(id, collection).catch(() => null);\nif (!existing) { await skillsApi.create(body); } // recreate instead\nelse { await skillsApi.createVersion(id, collection, files); }","handlingStrategy":"validation","validationCode":"const skill = await skillsApi.get(id, collection).catch(() => null);\nif (!skill) { await skillsApi.create({...body}); /* create instead of version */ }","typeGuard":"const canVersion = (s) => s != null && !s.readonly;","tryCatchPattern":"try { await skillsApi.createVersion(id, collection, files); }\ncatch (e) {\n  if (e.status === 404) { await skillsApi.create({...skillBody, files}); } // recreate\n  else throw e;\n}","preventionTips":["Guard version uploads behind a skill existence check","Refresh editor state before submitting versioned changes","Handle 'skill vanished' by offering recreate rather than a dead end"],"tags":["fastapi","http-404","skills","versioning","not-found"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}