neondatabase/neon · critical
max_keys_per_list_response can't be 0
Error message
max_keys_per_list_response can't be 0
What it means
AzureBlobStorage::new converts the configured max_keys_per_list_response into a NonZeroU32 for the Azure list-blobs page size, because Azure rejects a zero 'maxresults'. NonZeroU32::new(0) returns None, which becomes this anyhow error during remote storage client construction -- i.e. the pageserver/safekeeper fails at config-load/startup.
Source
Thrown at libs/remote_storage/src/azure_blob.rs:107
.context("trying to obtain Azure default credentials")?;
StorageCredentials::token_credential(token_credential)
};
let builder = ClientBuilder::new(account, credentials)
// we have an outer retry
.retry(RetryOptions::none())
// Customize transport to configure conneciton pooling
.transport(TransportOptions::new(Self::reqwest_client(
azure_config.conn_pool_size,
)));
let client = builder.container_client(azure_config.container_name.to_owned());
let max_keys_per_list_response =
if let Some(limit) = azure_config.max_keys_per_list_response {
Some(
NonZeroU32::new(limit as u32)
.ok_or_else(|| anyhow::anyhow!("max_keys_per_list_response can't be 0"))?,
)
} else {
None
};
Ok(AzureBlobStorage {
client,
container_name: azure_config.container_name.to_owned(),
prefix_in_container: azure_config.prefix_in_container.to_owned(),
max_keys_per_list_response,
concurrency_limiter: ConcurrencyLimiter::new(azure_config.concurrency_limit.get()),
timeout,
small_timeout,
/* BEGIN_HADRON */
put_block_size_mb: azure_config.put_block_size_mb,
/* END_HADRON */
})
}View on GitHub (pinned to 8f60b04da4)
Solutions
- Set max_keys_per_list_response to a positive value (typical page sizes are 100-5000, Azure caps at 5000)
- Omit the setting entirely to use the None branch (SDK default), rather than writing 0
- Fix templating/code that encodes 'no limit' as 0 -- omit the field or use NonZeroU32 in your config struct
- Add a config lint/unit test asserting the value is None or >0 before startup
Example fix
# before (remote_storage config) max_keys_per_list_response = 0 # after: either a positive page size max_keys_per_list_response = 1000 # or omit the key entirely for the default
Defensive patterns
Strategy: validation
Validate before calling
// Validate at config-parse time so the process fails with a precise message
// instead of deep inside AzureBlobStorage::new:
if let Some(limit) = &config.max_keys_per_list_response {
anyhow::ensure!(
*limit > 0,
"max_keys_per_list_response must be a positive u32 (1..=5000), got {limit}"
);
} Type guard
/// NonZero page-size guard; use it in the config struct to make 0 unrepresentable.
type MaxKeys = std::num::NonZeroU32;
fn as_max_keys(v: u32) -> Option<MaxKeys> {
MaxKeys::new(v)
} Try / catch
match remote_storage::GenericRemoteStorage::from_config(&remote_storage_config).await {
Err(e) if format!("{e:#}").contains("max_keys_per_list_response can't be 0") => {
// Config error, not transient: fix the config/secret and restart. Do not retry.
anyhow::bail!("invalid remote_storage config: {e:#}; set max_keys_per_list_response > 0 or omit it");
}
other => other?,
} Prevention
- Model the setting as Option<NonZeroU32> in config structs so 0 fails deserialization with a clear error
- Never encode 'unlimited' as 0 in templates; omit the key instead
- Add a config-validation unit test matrix (None, Some(0), Some(1), Some(5001)) to catch regressions
- Remember Azure's own cap is 5000 per list page -- values above get clamped or rejected upstream
When it happens
Trigger: Setting max_keys_per_list_response = 0 in the remote_storage Azure configuration (pageserver.toml / safekeeper config / env-provided config JSON). Construction of the Azure remote storage client aborts before serving any request.
Common situations: Config templating that maps 'unset/None' to 0 instead of omitting the key; copying a limit from another component where 0 means 'unlimited/default'; monitoring configs tuned down during load tests; hand-edited JSON configs.
Related errors
- pageserver connection information should be provided
- safekeeper connstrings should be provided
- tenant id should be provided
- timeline id should be provided
- shard {shard_index} missing from pageserver_connection_info
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/7ab81a044b918939.
Report an issue: GitHub.