{"record":{"id":"b23d0706b0e6c5b6","repo":"Hmbown/CodeWhale","slug":"sub-agent-token-budget-scope-must-not-be-empty","errorCode":null,"errorMessage":"Sub-agent token budget scope must not be empty","messagePattern":"Sub-agent token budget scope must not be empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":5247,"sourceCode":"        // cap is stored on the worker profile and checked separately; creating\n        // a new pool here would make its spend disappear from the parent.\n        let scope = self.inherited_budget_scope(parent_run_id).or_else(|| {\n            narrow_optional_limit(\n                self.default_token_budget,\n                positive_token_budget(requested_budget),\n            )\n            .map(|limit| (worker_id.to_string(), limit))\n        });\n\n        let Some((scope_id, limit)) = scope else {\n            return Ok(None);\n        };\n        self.resolve_budget_scope(scope_id, limit).map(Some)\n    }\n\n    fn resolve_budget_scope(&self, scope_id: String, limit: u64) -> Result<AgentUsageBudgetScope> {\n        if scope_id.trim().is_empty() {\n            return Err(anyhow!(\"Sub-agent token budget scope must not be empty\"));\n        }\n        let spent = self.aggregate_budget_spent(&scope_id);\n        let remaining = limit.saturating_sub(spent);\n        let work_remaining = remaining\n            .saturating_sub(budget_handback::token_reserve(limit))\n            .saturating_sub(self.reserved_handback_tokens(&scope_id, Some(&scope_id)));\n        if work_remaining < MIN_SUBAGENT_SPAWN_TOKEN_RESERVE {\n            return Err(anyhow!(\n                \"Sub-agent token budget exhausted for scope {scope_id}: {spent}/{limit} tokens spent, {remaining} remaining (including reserved reporting allowance). Wait for the parent/Workflow to summarize results or start a fresh agent run.\"\n            ));\n        }\n        Ok(AgentUsageBudgetScope {\n            scope_id,\n            limit,\n            spent,\n            remaining,\n        })\n    }","sourceCodeStart":5229,"sourceCodeEnd":5265,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/tools/subagent/mod.rs#L5229-L5265","documentation":"Thrown by `resolve_budget_scope` when a caller requests a sub-agent token-budget scope whose scope id is empty or whitespace-only. A scope id is the key under which spent tokens are aggregated and handback reserves are tracked, so an empty id cannot address any budget bucket.","triggerScenarios":"Calling the budget-resolution path (`resolve_budget_scope` via the entry that maps it at mod.rs:5247) with `scope_id` set to `\"\"` or only whitespace, typically because the caller derived the scope id from an unset field, an empty workflow name, or an untrimmed user input.","commonSituations":"A workflow/tool forgot to set the budget scope id; a config value was parsed as empty; programmatic callers passing `String::new()` as a default scope.","solutions":["Provide a non-empty scope id when requesting/setting the sub-agent token budget.","Trim or reject empty scope ids at the caller's input boundary before invoking the budget API.","Fall back to a well-known default scope name (e.g. the session or workflow id) when no explicit scope was supplied."],"exampleFix":"// before\nengine.set_token_budget(String::new(), 50_000);\n// after\nlet scope = if scope_id.trim().is_empty() { session_id.clone() } else { scope_id };\nengine.set_token_budget(scope, 50_000);","handlingStrategy":"validation","validationCode":"fn valid_scope(scope_id: &str) -> bool { !scope_id.trim().is_empty() }\nassert!(valid_scope(&scope_id), \"budget scope id must be non-empty\");","typeGuard":"fn as_scope_id(raw: &str) -> Option<&str> {\n    let t = raw.trim();\n    (!t.is_empty()).then_some(t)\n}","tryCatchPattern":null,"preventionTips":["Default to the session or workflow id when no explicit scope was configured.","Validate/trim scope ids at every input boundary (config parsing, tool arguments).","Never construct scope ids by string concatenation of possibly-empty fields without a fallback."],"tags":["rust","subagent","budget","validation"],"backgroundTag":"empty-required-field","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}