agentscope-ai/agentscope · error · ValueError

Not an S3 blob URI: {uri!r}

Error message

Not an S3 blob URI: {uri!r}

What it means

S3BlobStore._parse_uri splits s3://{bucket}/{key} URIs and rejects anything not starting with the s3:// scheme. It backs _key_from_uri and open, so passing a local:// URI, a bare key, or a plain path to an S3 store's URI-taking methods raises this ValueError immediately.

Source

Thrown at src/agentscope/app/rag/blob_store/_s3.py:179

                "S3BlobStore is not entered; use it inside "
                "``async with`` before calling blob methods.",
            )
        return self._session.client(
            "s3",
            region_name=self._region_name,
            endpoint_url=self._endpoint_url,
            aws_access_key_id=self._aws_access_key_id,
            aws_secret_access_key=self._aws_secret_access_key,
            aws_session_token=self._session_token,
            use_ssl=self._use_ssl,
            config=self._config,
        )

    @staticmethod
    def _parse_uri(uri: str) -> tuple[str, str]:
        """Split an ``s3://{bucket}/{key}`` URI into ``(bucket, key)``."""
        if not uri.startswith(_SCHEME):
            raise ValueError(f"Not an S3 blob URI: {uri!r}")
        rest = uri[len(_SCHEME) :]
        bucket, _, key = rest.partition("/")
        if not bucket or not key:
            raise ValueError(f"Malformed S3 blob URI: {uri!r}")
        return bucket, key

    @classmethod
    def _key_from_uri(cls, uri: str, expected_bucket: str) -> str:
        """Return the object key, asserting the bucket matches.

        Used by mutating operations (``delete``, ``exists``) where
        crossing into another bucket would be a bug — the configured
        bucket is the only place the store owns objects.
        """
        bucket, key = cls._parse_uri(uri)
        if bucket != expected_bucket:
            raise ValueError(
                f"Bucket {bucket!r} in URI {uri!r} does not match "

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Route by scheme: check uri.startswith('s3://') before handing it to S3BlobStore
  2. Store URIs returned by write_stream and pass them back untouched
  3. When migrating backends, copy/re-upload blobs and rewrite stored URIs
  4. For bare keys, construct f's3://{bucket}/{key}' or use write_stream with the key directly

Example fix

# before
async with s3store as s:
    await s.open('local://data/x')  # ValueError: Not an S3 blob URI

# after
async with s3store as s:
    await s.open('s3://my-bucket/data/x')
Defensive patterns

Strategy: validation

Validate before calling

def is_s3_uri(uri: str) -> bool:
    return uri.startswith('s3://')

if not is_s3_uri(uri):
    raise ValueError(f'refusing non-S3 URI for S3BlobStore: {uri!r}')
await store.open(uri)

Type guard

def is_s3_uri(uri: str) -> bool:
    return uri.startswith('s3://')

Try / catch

try:
    await store.open(uri)
except ValueError as e:
    if 'Not an S3 blob URI' in str(e):
        ...  # route uri to the correct backend by scheme
    raise

Prevention

When it happens

Trigger: Calling s3store.open('local://data/x'), s3store.exists('mykey'), or similar on S3BlobStore; also switching a configured backend from LocalBlobStore to S3BlobStore while keeping local:// URIs in the database.

Common situations: Mixed deployments (local dev with LocalBlobStore, prod with S3) where persisted blob URIs from one environment leak into the other; refactoring the blob backend behind a config flag without migrating stored URIs.

Related errors


AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28). Data as JSON: /api/errors/1cc958b91cb63ed3. Report an issue: GitHub.