{"record":{"id":"45dcc0e97605997b","repo":"headroomlabs-ai/headroom","slug":"retry-max-attempts-must-be-1-when-retry-enabled","errorCode":null,"errorMessage":"retry_max_attempts must be >= 1 when retry_enabled=True","messagePattern":"retry_max_attempts must be >= 1 when retry_enabled=True","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/proxy/models.py","lineNumber":520,"sourceCode":"\n    # Number of built-in uvicorn worker processes sharing this listen socket.\n    # Kept at the end to avoid shifting existing positional constructor fields.\n    # Process-local runtime hot reload is unsafe above one worker because only\n    # the worker receiving the admin request would observe the update.\n    worker_processes: int = 1\n\n    def __post_init__(self, smart_routing: bool | None = None) -> None:\n        if self.rollout is None:\n            self.rollout = resolve_rollout()\n        # ``read_maturation`` remains a concrete, already-resolved runtime\n        # setting for programmatic/config-file callers.  The CLI composition\n        # root derives it from this same snapshot before constructing the\n        # config; rewriting it here would resolve policy a second time and\n        # break explicit non-CLI configuration.\n        if self.worker_processes < 1:\n            raise ValueError(\"worker_processes must be >= 1\")\n        if self.retry_enabled and self.retry_max_attempts < 1:\n            raise ValueError(\"retry_max_attempts must be >= 1 when retry_enabled=True\")\n        # A 0 (or negative) requests-per-minute limit divides by zero in the\n        # token-bucket wait computation (rate_limit_policy.consume_from_bucket),\n        # 500-ing every request. The CLI already guards this with IntRange(min=1);\n        # fail fast here too so the JSON/programmatic config paths can't produce a\n        # limiter that crashes at request time. Only matters when limiting is on.\n        if self.rate_limit_enabled and self.rate_limit_requests_per_minute < 1:\n            raise ValueError(\n                \"rate_limit_requests_per_minute must be >= 1 when rate_limit_enabled=True\"\n            )\n\n    @property\n    def provider_api_overrides(self) -> ProviderApiOverrides:\n        \"\"\"Return provider API URL overrides as a dedicated provider config object.\"\"\"\n        return ProviderApiOverrides(\n            anthropic=self.anthropic_api_url,\n            openai=self.openai_api_url,\n            gemini=self.gemini_api_url,\n            cloudcode=self.cloudcode_api_url,","sourceCodeStart":502,"sourceCodeEnd":538,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/proxy/models.py#L502-L538","documentation":"The dataclass ProxyConfig validates in __post_init__ that retries cannot be enabled with an attempt count below 1. retry_enabled=True with retry_max_attempts=0 or negative is contradictory, so startup fails fast. This protects code that would otherwise retry zero times or loop incorrectly.","triggerScenarios":"Constructing ProxyConfig(retry_enabled=True, retry_max_attempts=0) or loading JSON/programmatic config where retry_max_attempts is absent and defaults to 0 while retry_enabled defaults to True; also YAML/env overrides setting attempts to 0.","commonSituations":"Copy-pasting a config template with retries on but attempts unset; migrating configs after a field rename; intentionally 'disabling' attempts while leaving retry_enabled true.","solutions":["Set retry_max_attempts to 1 or higher when retry_enabled is True.","If retries are unwanted, set retry_enabled=False instead of zeroing attempts.","Validate config files in CI by instantiating ProxyConfig before deploy."],"exampleFix":"# before\nProxyConfig(retry_enabled=True, retry_max_attempts=0)\n\n# after\nProxyConfig(retry_enabled=True, retry_max_attempts=3)\n# or\nProxyConfig(retry_enabled=False)","handlingStrategy":"validation","validationCode":"def check_retry(cfg):\n    if cfg.get(\"retry_enabled\", False) and cfg.get(\"retry_max_attempts\", 0) < 1:\n        raise SystemExit(\"Set retry_max_attempts >= 1 or disable retry_enabled\")","typeGuard":"def valid_retry(cfg) -> bool:\n    return not cfg.retry_enabled or cfg.retry_max_attempts >= 1","tryCatchPattern":"try:\n    ProxyConfig(**raw)\nexcept ValueError as e:\n    fail_config(e)  # surface before serve","preventionTips":["Validate config files by constructing ProxyConfig in CI.","Never use 0 attempts to mean 'disabled'; flip retry_enabled instead."],"tags":["config","validation","retries"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}