{"record":{"id":"0544ae4403c87010","repo":"agentscope-ai/agentscope","slug":"bucket-bucket-r-in-uri-uri-r-does-not-match-co","errorCode":null,"errorMessage":"Bucket {bucket!r} in URI {uri!r} does not match configured bucket {expected_bucket!r}.","messagePattern":"Bucket (.+?) in URI (.+?) does not match configured bucket (.+?)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/agentscope/app/rag/blob_store/_s3.py","lineNumber":196,"sourceCode":"        if not uri.startswith(_SCHEME):\n            raise ValueError(f\"Not an S3 blob URI: {uri!r}\")\n        rest = uri[len(_SCHEME) :]\n        bucket, _, key = rest.partition(\"/\")\n        if not bucket or not key:\n            raise ValueError(f\"Malformed S3 blob URI: {uri!r}\")\n        return bucket, key\n\n    @classmethod\n    def _key_from_uri(cls, uri: str, expected_bucket: str) -> str:\n        \"\"\"Return the object key, asserting the bucket matches.\n\n        Used by mutating operations (``delete``, ``exists``) where\n        crossing into another bucket would be a bug — the configured\n        bucket is the only place the store owns objects.\n        \"\"\"\n        bucket, key = cls._parse_uri(uri)\n        if bucket != expected_bucket:\n            raise ValueError(\n                f\"Bucket {bucket!r} in URI {uri!r} does not match \"\n                f\"configured bucket {expected_bucket!r}.\",\n            )\n        return key\n\n    async def write_stream(self, key: str, stream: IO[bytes]) -> str:\n        \"\"\"Stream-write a blob and return its ``s3://{bucket}/{key}`` URI.\n\n        Uses ``upload_fileobj`` so aioboto3 picks multipart upload\n        automatically for bodies above the multipart threshold\n        (8 MiB by default in botocore). For smaller bodies it\n        promotes to a single ``PutObject`` call.\n        \"\"\"\n        async with self._client() as s3:\n            await s3.upload_fileobj(stream, self._bucket, key)\n        return f\"{_SCHEME}{self._bucket}/{key}\"\n\n    @asynccontextmanager","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/app/rag/blob_store/_s3.py#L178-L214","documentation":"For mutating operations (delete, exists), S3BlobStore._key_from_uri asserts the bucket in the s3:// URI equals the store's configured bucket, since the store only owns objects in its own bucket. A mismatch raises this ValueError, preventing accidental cross-bucket deletes. (open/write paths parse the URI but this strict check applies to the mutating path.)","triggerScenarios":"Calling s3store.delete('s3://other-bucket/x') or s3store.exists(...) where the URI references a bucket different from the one passed to S3BlobStore(bucket=...).","commonSituations":"Multiple buckets (staging vs prod, per-tenant buckets) and URIs from one bucket passed to a store configured for another; copied URIs between environments; bucket renamed in config but old URIs still stored in the DB.","solutions":["Instantiate a store for the URI's bucket: async with S3BlobStore(bucket=uri_bucket) as s: await s.delete(uri)","Verify the bucket name in config matches where the blob was written","When renaming/migrating buckets, rewrite stored URIs to the new bucket","Add a pre-check helper: parse the URI and compare buckets before calling delete"],"exampleFix":"# before\nstore = S3BlobStore(bucket='prod-blobs')\nasync with store as s:\n    await s.delete('s3://staging-blobs/x')  # ValueError: bucket mismatch\n\n# after\nfrom urllib.parse import urlparse\nbucket = urlparse(uri).netloc\nasync with S3BlobStore(bucket=bucket) as s:\n    await s.delete(uri)","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef bucket_of(uri: str) -> str:\n    return urlparse(uri).netloc\n\nif bucket_of(uri) != configured_bucket:\n    raise ValueError(f'URI bucket {bucket_of(uri)!r} != store bucket {configured_bucket!r}')","typeGuard":"def uri_bucket_matches(uri: str, store_bucket: str) -> bool:\n    return uri.startswith('s3://') and urlparse(uri).netloc == store_bucket","tryCatchPattern":"try:\n    await store.delete(uri)\nexcept ValueError as e:\n    if 'does not match configured bucket' in str(e):\n        ...  # open a store for the URI's bucket and retry\n    raise","preventionTips":["Create per-bucket store instances instead of reusing one across buckets","Verify bucket names in config against stored blob URIs during deploy checks","After bucket renames, migrate stored URIs in the same change"],"tags":["s3","blob-store","bucket-mismatch","validation"],"backgroundTag":"bucket-mismatch","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}