{"record":{"id":"6ff7d143559efbe9","repo":"zylon-ai/private-gpt","slug":"unknown-skill-or-version-ids-in-collection-colle","errorCode":null,"errorMessage":"Unknown skill_or_version_ids in collection '{collection}': {', '.join(missing)}","messagePattern":"Unknown skill_or_version_ids in collection '(.+?)': (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"private_gpt/components/skills/repositories/skill_repository.py","lineNumber":368,"sourceCode":"        }\n\n        versions = {\n            row.id: row\n            for row in (\n                await session.scalars(\n                    select(SkillVersionORM)\n                    .join(SkillORM, SkillVersionORM.skill_id == SkillORM.id)\n                    .where(\n                        SkillORM.collection == collection,\n                        SkillVersionORM.id.in_(identifiers),\n                    )\n                )\n            ).all()\n        }\n\n        missing = sorted(identifiers - skills.keys() - versions.keys())\n        if missing:\n            raise ValueError(\n                f\"Unknown skill_or_version_ids in collection '{collection}': {', '.join(missing)}\"\n            )\n\n        result: list[SkillVersionEntity] = [\n            _version_from_orm(row) for row in versions.values()\n        ]\n\n        for skill_id in skills:\n            latest = await _latest_version_row_for_skill(session, skill_id)\n            if latest:\n                result.append(_version_from_orm(latest))\n\n        return result\n\n    async def _recover_all_latest_versions(\n        self, session: AsyncSession, skill_filter: SkillFilter\n    ) -> list[SkillVersionEntity]:\n        latest_versions = _latest_versions_subquery()","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/skills/repositories/skill_repository.py#L350-L386","documentation":"When resolving a batch of skill/version identifiers within a collection, the repository loads matching skills and versions and computes the set difference; any requested identifiers found in neither table raise ValueError listing the unknown ids. It guarantees callers cannot silently operate on ids that belong to another collection or do not exist — an integrity/authorization-style guard for cross-collection id confusion.","triggerScenarios":"Calling the batch resolution API (the method around skill_repository.py:368) with skill_or_version_ids containing an id that is deleted, belongs to a different collection, or is malformed (e.g. a stale id persisted by a client after the skill was removed).","commonSituations":"Client-side caches holding ids of since-deleted skills; copy-pasting ids between environments (dev id used against prod); a skill removed concurrently between listing and batch fetch; typos or truncated ids in requests.","solutions":["Re-list the collection's skills/versions and refresh the client's stored ids before retrying.","Filter out unknown ids up front if partial success is acceptable: resolve against the current listing first.","Verify the ids were not copied from another collection/environment; prefix/scope checks help.","Handle deletion races: on this error, invalidate the cache and re-fetch the id set once."],"exampleFix":"# before\nentities = await repo.resolve_many(collection, requested_ids)\n# ValueError: Unknown skill_or_version_ids ... (stale cached id)\n\n# after\nvalid_ids = {s.id for s in await repo.list_skills(collection)}\nentities = await repo.resolve_many(collection, [i for i in requested_ids if i in valid_ids])","handlingStrategy":"validation","validationCode":"async def filter_known_ids(repo, collection: str, ids: list[str]) -> list[str]:\n    known = {s.id for s in await repo.list_skills(collection)}\n    known |= {v.id for v in await repo.list_versions_all(collection)}  # adapt to actual API\n    missing = [i for i in ids if i not in known]\n    if missing:\n        logger.warning(\"Dropping unknown/stale ids: %s\", missing)\n    return [i for i in ids if i in known]","typeGuard":null,"tryCatchPattern":"try:\n    entities = await repo.resolve(collection, ids)\nexcept ValueError as e:\n    if \"Unknown skill_or_version_ids\" in str(e):\n        ids = await refresh_ids_and_filter(ids)  # invalidate cache, re-list, retry once\n        entities = await repo.resolve(collection, ids)\n    else:\n        raise","preventionTips":["Treat skill ids as ephemeral: re-list before batch operations.","Scope cached ids per collection and per environment to avoid cross-env leakage.","Invalidate client caches when a skill-deletion event is observed."],"tags":["skills","repository","identifiers","validation","stale-state"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}