{"record":{"id":"080ef375eb926fed","repo":"usestrix/strix","slug":"scan-budget-reached","errorCode":null,"errorMessage":"scan budget reached","messagePattern":"scan budget reached","errorType":"exception","errorClass":"BudgetExceededError","httpStatus":null,"severity":"error","filePath":"strix/core/execution.py","lineNumber":202,"sourceCode":"    event_sink: StreamEventSink | None = None,\n    hooks: RunHooks[dict[str, Any]] | None = None,\n) -> RunResultBase | None:\n    await coordinator.attach_runtime(\n        agent_id,\n        session=session,\n        interrupt_on_message=interactive,\n    )\n    result: RunResultBase | None = None\n\n    first_cycle_input = await _seed_and_prepare_first_input(\n        session, initial_input, start_parked=start_parked\n    )\n\n    budget_stopped = coordinator.budget_stopped\n    reserve_stopped = coordinator.reserve_stopped\n    if budget_stopped:\n        await coordinator.set_status(agent_id, \"stopped\")\n        raise BudgetExceededError(\"scan budget reached\")\n    if reserve_stopped and context.get(\"parent_id\") is not None:\n        await coordinator.set_status(agent_id, \"stopped\")\n        raise SubagentBudgetReservedError(\"scan reached the sub-agent budget reserve\")\n\n    if reserve_stopped and start_parked and interactive and context.get(\"parent_id\") is None:\n        await coordinator.send(agent_id, _reserve_notice())\n\n    if not (start_parked and interactive):\n        with contextlib.suppress(BudgetPausedError):\n            result = await _run_until_lifecycle(\n                agent,\n                coordinator,\n                agent_id,\n                initial_input=first_cycle_input,\n                run_config=run_config,\n                context=context,\n                max_turns=max_turns,\n                session=session,","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/core/execution.py#L184-L220","documentation":"BudgetExceededError('scan budget reached') raised in `_run_agent` after the first input cycle when `coordinator.budget_stopped` is set: total LLM spend across the scan reached `max_budget_usd`. The agent is marked 'stopped' and the error propagates to terminate the run. (In interactive mode the hook instead raises BudgetPausedError which is suppressed here; this raise fires for the non-interactive/headless path or when the coordinator already recorded a stop.)","triggerScenarios":"A headless scan (`strix -n`) with `--max-budget N` where cumulative LLM cost recorded in report_state crosses N during the run; the coordinator's budget_stopped flag is observed after seeding the first input.","commonSituations":"Deep scan modes on large targets exhausting the budget; long autonomous runs where cost accumulates faster than expected; budget set too low relative to target size (e.g. --max-budget 1 on a deep scan).","solutions":["Re-run with a higher budget: `strix -n -t ./ --max-budget 50`","Use `--scan-mode quick` or narrow the target to reduce cost","For interactive runs, the budget pauses instead of stopping — continue from the TUI after reviewing spend, or use extend_budget semantics","Review the run artifacts (run.json llm_usage.cost) to see where cost went before re-running"],"exampleFix":"# before\nstrix -n -t ./ --scan-mode deep --max-budget 5   # BudgetExceededError: scan budget reached\n# after\nstrix -n -t ./ --scan-mode deep --max-budget 25","handlingStrategy":"validation","validationCode":"# size the budget before launching: quick scans of small targets need ~1-5 USD, deep scans often 10-50+\nimport subprocess, json\n\ndef budget_from_previous_run(runs_dir: str, fallback: float = 10.0) -> float:\n    # read the last run.json cost and add headroom\n    try:\n        run = sorted(Path(runs_dir).glob(\"*/run.json\"))[-1]\n        cost = json.loads(run.read_text())[\"llm_usage\"][\"cost\"]\n        return max(fallback, cost * 1.5)\n    except (OSError, KeyError, IndexError):\n        return fallback","typeGuard":null,"tryCatchPattern":"from strix.core.exceptions import BudgetExceededError  # module path per repo layout\n\ntry:\n    run_scan(target=\"./\", scan_mode=\"quick\", max_budget=10)\nexcept BudgetExceededError as e:\n    # partial artifacts still written — inspect before re-running with a larger budget\n    ...","preventionTips":["Start with quick mode to calibrate cost, then size --max-budget from run.json before deep runs","Route sub-agents to cheaper models so the same budget buys more coverage","In CI, exit-code 2 + run.json cost tells you whether to raise budget or shrink scope — automate that decision"],"tags":["budget","cost-control","scan","headless"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}