{"record":{"id":"f60019ea051faa3b","repo":"agentscope-ai/agentscope","slug":"credential-credential-id-r-for-owner-owner-id-r","errorCode":null,"errorMessage":"Credential {credential_id!r} for owner {owner_id!r} disappeared immediately after a successful upsert.","messagePattern":"Credential (.+?) for owner (.+?) disappeared immediately after a successful upsert\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/agentscope/app/_router/_credential.py","lineNumber":154,"sourceCode":"            caller; 403 if visible but only readable.\n    \"\"\"\n    owner_id, _ = await access.resolve_for_edit(\n        user_id,\n        ResourceKind.CREDENTIAL,\n        credential_id,\n    )\n\n    credential = CredentialFactory.from_dict(body.data)\n    credential.id = credential_id\n    await storage.upsert_credential(owner_id, credential)\n    # ``resolve_for_edit`` proved the record existed under ``owner_id``\n    # and the upsert above just wrote back to the same key, so the read\n    # is a value refresh, not an existence check. If it still comes back\n    # empty (e.g. a concurrent delete), surface an explicit server error\n    # rather than relying on ``assert`` (which ``-O`` strips).\n    updated = await storage.get_credential(owner_id, credential_id)\n    if updated is None:\n        raise RuntimeError(\n            f\"Credential {credential_id!r} for owner {owner_id!r} \"\n            \"disappeared immediately after a successful upsert.\",\n        )\n    # Only reachable via ``resolve_for_edit``, so the caller has edit\n    # permission by construction.\n    return CredentialView.model_validate(\n        {**updated.model_dump(), \"editable\": True},\n    )\n\n\n@credential_router.delete(\n    \"/{credential_id}\",\n    status_code=status.HTTP_204_NO_CONTENT,\n    summary=\"Delete a credential\",\n)\nasync def delete_credential(\n    credential_id: str,\n    user_id: str = Depends(get_current_user_id),","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/app/_router/_credential.py#L136-L172","documentation":"Raised when a credential that was just successfully upserted cannot be read back from storage immediately afterwards. The code deliberately re-reads the record after upsert to refresh its value, and a None result means a concurrent delete (or a storage-layer inconsistency) removed it between the write and the read. It is a plain RuntimeError rather than an HTTPException because it indicates a server-side invariant violation, not a caller mistake.","triggerScenarios":"Calling the credential update API while another request concurrently deletes the same credential_id for the same owner; or a broken/inconsistent storage backend where the upsert does not persist the key it claims to have written.","commonSituations":"Race conditions in tests that delete credentials in parallel; a mock or in-memory storage implementation whose upsert/get keys don't match; a database replication lag scenario where the read hits a stale replica.","solutions":["Check for concurrent delete requests against the same (owner_id, credential_id) and serialize them (e.g. retry the update after the delete completes, or use optimistic concurrency)","Verify your storage backend actually persists the upsert before the subsequent get (check for rollback, failed flush, or key mismatch in a custom Storage implementation)","If using a read replica, ensure read-your-writes consistency for this path","Report as a server bug if reproducible with the built-in storage and no concurrent deletes"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    await client.update_credential(owner_id, credential_id, patch)\nexcept RuntimeError as e:\n    if \"disappeared immediately\" in str(e):\n        await asyncio.sleep(0.2)\n        await client.update_credential(owner_id, credential_id, patch)  # or re-read state\n    else:\n        raise","preventionTips":["Avoid concurrent delete+update of the same credential from parallel tasks","Wrap updates in a read-modify-write loop that re-reads state on RuntimeError","Monitor storage health if this fires without concurrent writes"],"tags":["storage","race-condition","credential","server-error"],"backgroundTag":"read-after-write-inconsistency","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}