Hmbown/CodeWhale · error · anyhow::Error
Tool is not available to this read-only worker because its…
Error message
Tool {name} is not available to this read-only worker because its process path does not share the hardened evidence boundary. Use read/search, classifier-bounded bash reads, or the verifier's bounded Run tool instead. What it means
Read-only Fleet roles (e.g. scout/reviewer workers) run with a hardened evidence boundary: their process path cannot execute unhardened process tools. `role_blocks_unhardened_process_tool` intercepts such a call and rejects it, directing the worker to the read/search tools, classifier-bounded bash reads, or the verifier's bounded Run tool.
Solutions
- Use the read/search tools or classifier-bounded bash reads instead of the unhardened process tool.
- Route the execution through the verifier's bounded Run tool, which shares the hardened boundary.
- Re-delegate the task to an `implement` or `general` role that has write/execute authority.
- Update the orchestrating prompt/tool catalog so read-only workers are not offered the unhardened tool.
Example fix
// before
// read-only worker:
Bash.run("cat /evidence/log.txt")
// after
Read.read("/evidence/log.txt") Defensive patterns
Strategy: validation
Validate before calling
const UNHARDENED = ['Bash', 'Run'];
if (role === 'scout' || role === 'reviewer') {
if (UNHARDENED.includes(toolName)) throw new Error('use read/search or bounded Run instead');
} Type guard
const isReadOnlyRole = (t) => ['scout','reviewer'].includes(t);
Try / catch
try { result = await callTool(name, input); } catch (e) { if (String(e).includes('hardened evidence boundary')) { result = await readOrBoundedAlternative(name, input); } } Prevention
- Match tool choice to the role: read-only workers use read/search or verifier-bounded Run
- Do not copy parent tool usage patterns into read-only children
- State execution constraints in the delegation prompt
When it happens
Trigger: A read-only role sub-agent invokes a process-execution tool (e.g. `Bash`/`Run` in unhardened form) whose process path does not share the hardened evidence boundary; detected by `role_blocks_unhardened_process_tool(name)` before dispatch.
Common situations: The delegating prompt tells a scout to run arbitrary shell commands; a generic agent loops over its full tool catalog without honoring role restrictions; a child attempts the same tool a parent (with write authority) just used successfully.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- agent action=claim widens an enforced write scope, and the…
- Tool Web is limited to search/fetch in the read-only…
- append_allow_rules only accepts action = "allow"
- Codewhale credentials directory must be owned by the…
- Codewhale-owned credential file must be singly linked…
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/c5dcd9a2ced29ba7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/tools/subagent/mod.rs:17483
match &self.allowed_tools {
None => Vec::new(),
Some(list) => list
.iter()
.filter(|name| !is_tool_search_tool(name) && !self.registry.contains(name))
.cloned()
.collect(),
}
}
async fn execute_full(
&self,
agent_id: &str,
tool_id: &str,
name: &str,
input: Value,
) -> Result<RichToolResult> {
if self.role_blocks_unhardened_process_tool(name) {
return Err(anyhow!(
"Tool {name} is not available to this read-only worker because its process path does not share the hardened evidence boundary. Use read/search, classifier-bounded bash reads, or the verifier's bounded Run tool instead."
));
}
let action = input.get("action").and_then(Value::as_str);
if matches!(&self.agent_type, FleetRole::Scout | FleetRole::Reviewer)
&& name == "Web"
&& !matches!(action, Some("search" | "fetch"))
{
return Err(anyhow!(
"Tool Web is limited to search/fetch in the read-only evidence profile"
));
}
// Catalog shaping is not authority. `agent` clears both name-keyed
// gates below by design, so the per-action gate has to be repeated
// here or a hand-written call would reach an action the role's own
// catalog withheld.
if name == "agent"
&& matches!(parse_agent_tool_action(&input), Ok(AgentToolAction::Claim))View on GitHub (pinned to 433685b202)