{"record":{"id":"7ab81a044b918939","repo":"neondatabase/neon","slug":"max-keys-per-list-response-can-t-be-0","errorCode":null,"errorMessage":"max_keys_per_list_response can't be 0","messagePattern":"max_keys_per_list_response can't be 0","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"libs/remote_storage/src/azure_blob.rs","lineNumber":107,"sourceCode":"                .context(\"trying to obtain Azure default credentials\")?;\n            StorageCredentials::token_credential(token_credential)\n        };\n\n        let builder = ClientBuilder::new(account, credentials)\n            // we have an outer retry\n            .retry(RetryOptions::none())\n            // Customize transport to configure conneciton pooling\n            .transport(TransportOptions::new(Self::reqwest_client(\n                azure_config.conn_pool_size,\n            )));\n\n        let client = builder.container_client(azure_config.container_name.to_owned());\n\n        let max_keys_per_list_response =\n            if let Some(limit) = azure_config.max_keys_per_list_response {\n                Some(\n                    NonZeroU32::new(limit as u32)\n                        .ok_or_else(|| anyhow::anyhow!(\"max_keys_per_list_response can't be 0\"))?,\n                )\n            } else {\n                None\n            };\n\n        Ok(AzureBlobStorage {\n            client,\n            container_name: azure_config.container_name.to_owned(),\n            prefix_in_container: azure_config.prefix_in_container.to_owned(),\n            max_keys_per_list_response,\n            concurrency_limiter: ConcurrencyLimiter::new(azure_config.concurrency_limit.get()),\n            timeout,\n            small_timeout,\n            /* BEGIN_HADRON */\n            put_block_size_mb: azure_config.put_block_size_mb,\n            /* END_HADRON */\n        })\n    }","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/azure_blob.rs#L89-L125","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (remote_storage config)\nmax_keys_per_list_response = 0\n\n# after: either a positive page size\nmax_keys_per_list_response = 1000\n# or omit the key entirely for the default","handlingStrategy":"validation","validationCode":"// Validate at config-parse time so the process fails with a precise message\n// instead of deep inside AzureBlobStorage::new:\nif let Some(limit) = &config.max_keys_per_list_response {\n    anyhow::ensure!(\n        *limit > 0,\n        \"max_keys_per_list_response must be a positive u32 (1..=5000), got {limit}\"\n    );\n}","typeGuard":"/// NonZero page-size guard; use it in the config struct to make 0 unrepresentable.\ntype MaxKeys = std::num::NonZeroU32;\n\nfn as_max_keys(v: u32) -> Option<MaxKeys> {\n    MaxKeys::new(v)\n}","tryCatchPattern":"match remote_storage::GenericRemoteStorage::from_config(&remote_storage_config).await {\n    Err(e) if format!(\"{e:#}\").contains(\"max_keys_per_list_response can't be 0\") => {\n        // Config error, not transient: fix the config/secret and restart. Do not retry.\n        anyhow::bail!(\"invalid remote_storage config: {e:#}; set max_keys_per_list_response > 0 or omit it\");\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["rust","azure","remote-storage","configuration","startup"],"backgroundTag":"invalid-config-value","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}