{"record":{"id":"ee49c7a903b26ddb","repo":"usestrix/strix","slug":"sub-agent-budget-reserve-reached-spent-cost-4f","errorCode":null,"errorMessage":"Sub-agent budget reserve reached: spent ${cost:.4f} of ${self._max_budget_usd:.2f} (>= {round(_SUBAGENT_BUDGET_RESERVE * 100)}% reserve); stopping this sub-agent so the root agent can finish the scan.","messagePattern":"Sub-agent budget reserve reached: spent (.+?) of (.+?) \\(>= (.+?)% reserve\\); stopping this sub-agent so the root agent can finish the scan\\.","errorType":"exception","errorClass":"SubagentBudgetReservedError","httpStatus":null,"severity":"warning","filePath":"strix/core/hooks.py","lineNumber":268,"sourceCode":"        except Exception:\n            logger.exception(\"failed to record SDK usage for agent %s\", agent_id)\n\n        if self._max_budget_usd is not None:\n            cost = report_state.get_total_llm_cost()\n            if cost >= self._max_budget_usd:\n                if self._interactive:\n                    raise BudgetPausedError(\n                        f\"Scan budget of ${self._max_budget_usd:.2f} reached \"\n                        f\"(spent ${cost:.4f}); pausing until the user continues\"\n                    )\n                raise BudgetExceededError(\n                    f\"Token budget of ${self._max_budget_usd:.2f} exceeded (spent ${cost:.4f})\"\n                )\n            is_root = ctx.get(\"parent_id\") is None\n            if not self._interactive and not is_root:\n                reserve_limit = self._max_budget_usd * _SUBAGENT_BUDGET_RESERVE\n                if cost >= reserve_limit:\n                    raise SubagentBudgetReservedError(\n                        f\"Sub-agent budget reserve reached: spent ${cost:.4f} of \"\n                        f\"${self._max_budget_usd:.2f} \"\n                        f\"(>= {round(_SUBAGENT_BUDGET_RESERVE * 100)}% reserve); stopping this \"\n                        \"sub-agent so the root agent can finish the scan.\"\n                    )\n","sourceCodeStart":250,"sourceCodeEnd":274,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/core/hooks.py#L250-L274","documentation":"Raised by the budget hook (strix/core/hooks.py:268) when a non-root (sub-agent) LLM call pushes total scan cost to or past 90% of max_budget_usd (_SUBAGENT_BUDGET_RESERVE = 0.90) while running headless (non-interactive). The last 10% of the budget is reserved so the root agent can still finish, write its report, and shut down cleanly. It subclasses RuntimeError and is caught internally in strix/core/execution.py, where it marks the sub-agent as stopped rather than aborting the whole scan.","triggerScenarios":"Running strix headless (-n) with --max-budget N while sub-agents burn tokens; on each SDK response the hook compares report_state.get_total_llm_cost() against max_budget_usd * 0.90 and raises when a sub-agent (ctx parent_id is not None) crosses that reserve threshold.","commonSituations":"Deep scan modes that fan out many sub-agents; cheap max-budget values (e.g. $1-5) with expensive models; cost spikes from large tool outputs inflating token usage.","solutions":["No action needed for the scan itself: the root agent finishes and still produces a report; check run.json and penetration_test_report.md for coverage.","Increase the ceiling: rerun with a larger --max-budget so sub-agents are not cut off at 90% of it.","Switch to interactive mode (drop -n) — the reserve check is skipped when interactive, and the full budget raises BudgetPausedError instead so the user can continue.","Use a cheaper model (STRIX_LLM / model=) to keep sub-agent spend under the reserve.","Resume the scan (strix resume / is_resume path) with a higher budget to continue where sub-agents stopped."],"exampleFix":"# before\nstrix -n -t https://example.com --max-budget 2   # sub-agents stop at $1.80\n\n# after\nstrix -n -t https://example.com --max-budget 10  # reserve is $9.00, sub-agents finish","handlingStrategy":"try-catch","validationCode":"from strix.core.hooks import SubagentBudgetReservedError  # subclass of RuntimeError\n# Pre-check before spawning more sub-agent work:\nspent = report_state.get_total_llm_cost()\nif max_budget_usd is not None and spent >= max_budget_usd * 0.90:\n    print('sub-agent reserve reached; root agent should wrap up')","typeGuard":"def is_subagent_budget_reserve(exc: BaseException) -> bool:\n    return isinstance(exc, RuntimeError) and 'Sub-agent budget reserve' in str(exc)","tryCatchPattern":"try:\n    await run_strix_scan(...)\nexcept SubagentBudgetReservedError:\n    # non-fatal: root agent still finishes and writes the report\n    logging.warning('sub-agents stopped at budget reserve; report still produced')","preventionTips":["Size --max-budget so 90% of it covers expected sub-agent spend.","Prefer cheaper models for deep multi-sub-agent scans.","Treat this as a warning, not a failure: always read the produced report and run.json."],"tags":["budget","subagent","llm-cost","headless"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}