{"record":{"id":"5d27c7bec4e83294","repo":"headroomlabs-ai/headroom","slug":"target-savings-must-be-between-0-and-1","errorCode":null,"errorMessage":"target_savings must be between 0 and 1","messagePattern":"target_savings must be between 0 and 1","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/agent_savings.py","lineNumber":380,"sourceCode":"\n    Deliberately NOT called from ``_proxy_config_from_env`` / ``ContentRouter`` or\n    any other library-level builder that unit tests construct directly, so those\n    keep clean (unseeded) defaults and test isolation is preserved.\n    \"\"\"\n    target = os.environ if env is None else env\n    # apply_agent_savings_env_defaults honors an explicit HEADROOM_SAVINGS_PROFILE\n    # already in the env and otherwise falls back to DEFAULT_PROFILE (coding).\n    apply_agent_savings_env_defaults(target)\n\n\ndef with_target_savings(\n    profile: AgentSavingsProfile,\n    target_savings: float,\n) -> AgentSavingsProfile:\n    \"\"\"Return a copy of ``profile`` adjusted to a specific savings target.\"\"\"\n\n    if not 0 < target_savings < 1:\n        raise ValueError(\"target_savings must be between 0 and 1\")\n    return replace(\n        profile,\n        target_savings=target_savings,\n        target_ratio=round(1 - target_savings, 4),\n    )\n","sourceCodeStart":362,"sourceCodeEnd":386,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/agent_savings.py#L362-L386","documentation":"with_target_savings rejects any target_savings outside the open interval (0, 1). The check is strict (0 < x < 1), so exactly 0 or exactly 1 are also rejected, because a 0% or 100% savings target produces a degenerate target_ratio (1.0 or 0.0) that downstream budget math cannot use meaningfully.","triggerScenarios":"Calling with_target_savings(profile, 0.0), with_target_savings(profile, 1.0), a negative value, or a value greater than 1. Values like 0.05 or 0.95 are fine.","commonSituations":"Computing target_savings from a CLI flag or env var without bounds-checking ('--savings 100' meaning 100 percent instead of 1.0), integer division yielding 0, or treating the parameter as inclusive 0..=1.","solutions":["Pass a fraction strictly between 0 and 1: 0.5 means 50% savings.","If your input is a percentage, divide by 100 and clamp to the open interval before calling: max(min(pct/100, 0.999), 0.001).","If you genuinely need 'no savings' or 'full savings', pick a boundary-adjacent value (e.g. 0.0001) or bypass this helper, since the function intentionally forbids the exact endpoints."],"exampleFix":"# before\nprofile = with_target_savings(profile, float(os.environ[\"SAVINGS_PCT\"]))  # 75 -> ValueError\n\n# after\npct = float(os.environ[\"SAVINGS_PCT\"]) / 100\nprofile = with_target_savings(profile, min(max(pct, 0.001), 0.999))","handlingStrategy":"validation","validationCode":"def _clamp_target_savings(value: float) -> float:\n    \"\"\"Coerce to the open interval (0, 1) required by with_target_savings.\"\"\"\n    if not 0 < value < 1:\n        raise ValueError(f\"target_savings={value!r} must be in the open interval (0, 1)\")\n    return value\n\n# use before the call:\ntarget = float(os.environ.get(\"HEADROOM_SAVINGS\", \"0.5\")) / 100 if float(os.environ.get(\"HEADROOM_SAVINGS\", \"50\")) > 1 else float(os.environ.get(\"HEADROOM_SAVINGS\", \"0.5\"))\n_clamp_target_savings(target)","typeGuard":"def is_valid_target_savings(x: object) -> bool:\n    return isinstance(x, (int, float)) and not isinstance(x, bool) and 0 < x < 1","tryCatchPattern":"try:\n    profile = with_target_savings(profile, target)\nexcept ValueError as e:\n    if \"target_savings\" in str(e):\n        raise SystemExit(f\"--savings must be a fraction in (0,1), got {target!r}; did you mean {target/100}?\" )\n    raise","preventionTips":["Document the parameter as a fraction, never a percentage, at every CLI/env boundary.","Validate at the config-parsing layer so the deep helper never sees garbage.","Remember the interval is open: 0 and 1 are invalid by design."],"tags":["python","validation","savings","range-check"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}