nautechsystems/nautilus_trader · error · anyhow::Error
Cross-store URI {path} (root {}) does not belong to catalog
Error message
Cross-store URI {path} (root {}) does not belong to catalog rooted at {} ({}) What it means
After confirming the URI scheme is remote, `remote_uri_object_path` compares the store root of the given path (bucket/container) with the store root of the catalog's `original_uri`. If they differ, the path belongs to another object store and would cross store boundaries, so the operation is rejected. The catalog only operates on data under its own root store.
Source
Thrown at crates/persistence/src/backend/catalog.rs:3064
Ok(self.path_without_local_base(&normalized_path))
}
fn remote_uri_object_path(&self, path: &str) -> anyhow::Result<String> {
let path_url = url::Url::parse(path)
.map_err(|e| anyhow::anyhow!("Failed to parse object store URI {path}: {e}"))?;
if !is_remote_uri_scheme(path_url.scheme()) {
anyhow::bail!(
"URI {path} uses non-remote scheme {} for remote catalog at {}",
path_url.scheme(),
self.original_uri,
);
}
let catalog_root = remote_store_root_url(&self.original_uri)?;
let path_root = remote_store_root_url(path)?;
if catalog_root.as_str().trim_end_matches('/') != path_root.as_str().trim_end_matches('/') {
anyhow::bail!(
"Cross-store URI {path} (root {}) does not belong to catalog rooted at {} ({})",
path_root.as_str().trim_end_matches('/'),
self.original_uri,
catalog_root.as_str().trim_end_matches('/'),
);
}
// The URL crate keeps the path component percent-encoded (e.g. `%5E`),
// so preserve that encoding for `ObjectPath::parse` round-trips through
// `object_store::list`/`get`.
Ok(path_url.path().trim_start_matches('/').to_string())
}
fn path_without_local_base(&self, path: &str) -> String {
let base_path = if self.base_path.is_empty() {
self.native_base_path_string()
} else {
self.base_path.clone()View on GitHub (pinned to 18893faf8b)
Solutions
- Point the path at the same bucket/container root as the catalog URI (compare the roots in the error message: 'root X' vs 'catalog rooted at Y').
- Re-create the catalog with the URI of the store you actually want to operate on, so the roots match.
- Move/copy the data into the catalog's root store if it legitimately belongs under this catalog.
Example fix
// before
catalog.object_store_path("gs://other-bucket/data/quotes.parquet"); // catalog rooted at s3://my-bucket
// after
catalog.object_store_path("s3://my-bucket/data/quotes.parquet"); Defensive patterns
Strategy: validation
Validate before calling
// Rust: check the path shares the catalog's store root before calling
let catalog_root = "s3://my-bucket";
let path_root = path.split("/").take(3).collect::<Vec<_>>().join("/");
assert_eq!(catalog_root.trim_end_matches('/'), path_root.trim_end_matches('/'), "path must be under catalog root"); Prevention
- Derive all data paths from the catalog's base URI instead of hardcoding buckets.
- Use environment-specific config (one bucket per env) and read the bucket name from the same config as the catalog URI.
- Log the catalog root at startup and keep path-building code in one shared helper.
When it happens
Trigger: Calling `catalog.object_store_path(path)` (via `remote_uri_object_path`) where the path's remote root (e.g. `s3://bucket-b`) differs from the catalog root (e.g. `s3://bucket-a`), including trailing-slash variants that would otherwise normalize identically.
Common situations: Copying paths between environments (staging bucket vs production bucket); multi-bucket setups where the catalog was initialized with one bucket but code references another; region-specific bucket names (`s3://us-east-data` vs `s3://eu-data`); switching cloud providers (GCS path against S3-rooted catalog).
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- URI {path} uses non-remote scheme {} for remote catalog at {
- replace_existing for remote streaming paths requires a non-e
- Unsupported blockchain {blockchain} for RPC connection
- Kraken Spot does not support the demo environment
- Redis config error: username supplied without password. Eith
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/635f3574b20144fb.
Report an issue: GitHub.