agentscope-ai/agentscope · error · RuntimeError
Credential {credential_id!r} for owner {owner_id!r} disappea
Error message
Credential {credential_id!r} for owner {owner_id!r} disappeared immediately after a successful upsert. What it means
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.
Source
Thrown at src/agentscope/app/_router/_credential.py:154
caller; 403 if visible but only readable.
"""
owner_id, _ = await access.resolve_for_edit(
user_id,
ResourceKind.CREDENTIAL,
credential_id,
)
credential = CredentialFactory.from_dict(body.data)
credential.id = credential_id
await storage.upsert_credential(owner_id, credential)
# ``resolve_for_edit`` proved the record existed under ``owner_id``
# and the upsert above just wrote back to the same key, so the read
# is a value refresh, not an existence check. If it still comes back
# empty (e.g. a concurrent delete), surface an explicit server error
# rather than relying on ``assert`` (which ``-O`` strips).
updated = await storage.get_credential(owner_id, credential_id)
if updated is None:
raise RuntimeError(
f"Credential {credential_id!r} for owner {owner_id!r} "
"disappeared immediately after a successful upsert.",
)
# Only reachable via ``resolve_for_edit``, so the caller has edit
# permission by construction.
return CredentialView.model_validate(
{**updated.model_dump(), "editable": True},
)
@credential_router.delete(
"/{credential_id}",
status_code=status.HTTP_204_NO_CONTENT,
summary="Delete a credential",
)
async def delete_credential(
credential_id: str,
user_id: str = Depends(get_current_user_id),View on GitHub (pinned to e90f1c7592)
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
Defensive patterns
Strategy: retry
Try / catch
try:
await client.update_credential(owner_id, credential_id, patch)
except RuntimeError as e:
if "disappeared immediately" in str(e):
await asyncio.sleep(0.2)
await client.update_credential(owner_id, credential_id, patch) # or re-read state
else:
raise Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Credential {record.data.embedding_model_config.credential_id
- module 'agentscope.app.storage' has no attribute {name!r}
- This storage backend has no channel support.
- File {path_file} does not exist.
- PVC {pvc_name!r} did not finish deleting within {timeout}s
AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28).
Data as JSON: /api/errors/f60019ea051faa3b.
Report an issue: GitHub.