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

  1. Use the read/search tools or classifier-bounded bash reads instead of the unhardened process tool.
  2. Route the execution through the verifier's bounded Run tool, which shares the hardened boundary.
  3. Re-delegate the task to an `implement` or `general` role that has write/execute authority.
  4. 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

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


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)