Hmbown/CodeWhale · error

fleet task '{}' is write-capable but declares no workspace.w

Error message

fleet task '{}' is write-capable but declares no workspace.writable_paths or metadata.coordination_contracts

What it means

The worker's effective runtime profile allows writes (permissions.write = true, possibly derived from a parent profile or the role's default), but the task declares neither `workspace.writable_paths` nor `metadata.coordination_contracts`. The fleet refuses write-capable tasks with no declared write surface: there would be no bounded, auditable place for the worker to write or coordinate. This check sits in the sub-agent worker spec builder (crates/tui/src/fleet/worker_runtime.rs) before any launch manifest is minted.

Source

Thrown at crates/tui/src/fleet/worker_runtime.rs:241

    requested_runtime.provider = explicit_fleet_provider_id(agent_profile);
    if let Some(reasoning_effort) = effective_fleet_reasoning_effort(agent_profile) {
        requested_runtime.reasoning_effort = Some(reasoning_effort);
    }
    if let Some(agent_profile) = agent_profile
        && let Some(profile_depth) = agent_profile.profile.delegation.max_spawn_depth
    {
        requested_runtime.max_spawn_depth = requested_runtime.max_spawn_depth.min(profile_depth);
    }
    let runtime_profile = parent_runtime_profile
        .map(|parent| parent.derive_child(&requested_runtime))
        .unwrap_or(requested_runtime);
    let writable_roots = fleet_write_roots(task_spec)?;
    let coordination_contracts = fleet_coordination_contracts(task_spec)?;
    if runtime_profile.permissions.write
        && writable_roots.is_empty()
        && coordination_contracts.is_empty()
    {
        bail!(
            "fleet task '{}' is write-capable but declares no workspace.writable_paths or metadata.coordination_contracts",
            task_spec.id
        );
    }
    let session_name = format!("fleet-{}-{}", worker_id, task_spec.id);
    let launch_manifest = ChildLaunchManifest {
        owner_session: run_id.to_string(),
        child_id: worker_id.to_string(),
        profile: runtime_profile.clone(),
        prompt: objective.clone(),
        cwd: Some(workspace.display().to_string()),
        worktree: worker_workspace_is_isolated(coordination_workspace, workspace),
        writable_roots,
        writable_files: Vec::new(),
        coordination_contracts,
        expected_artifact: None,
        token_budget: task_spec
            .budget

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Add `writable_paths` under `[workspace]` listing the repo-relative roots the task may write (e.g. "src", ".codewhale/fleet").
  2. If the task coordinates through contracts instead, add `metadata.coordination_contracts = ["..."]` (array of short strings).
  3. If the task genuinely needs no writes, switch it to a read-only role/profile so permissions.write is false.

Example fix

# before
[[tasks]]
id = "refactor-core"
role = "implementer"
# no workspace.writable_paths, no coordination_contracts

# after
[[tasks]]
id = "refactor-core"
role = "implementer"
[workspace]
writable_paths = ["src"]
Defensive patterns

Strategy: validation

Validate before calling

fn write_scope_declared(task: &FleetTaskSpec) -> bool {
    let writable = task.workspace.as_ref().is_some_and(|w| !w.writable_paths.is_empty());
    let contracts = task
        .metadata
        .get("coordination_contracts")
        .and_then(|v| v.as_array())
        .is_some_and(|a| !a.is_empty());
    writable || contracts
}

Prevention

When it happens

Trigger: A task using a write-capable role/agent type with only `workspace.readonly_paths` set; a parent runtime profile with write=true derived into the child while the task omits both fields; metadata present but missing the `coordination_contracts` key.

Common situations: Copying a read-only task template and flipping the role to an implementer type without adding write scoping; assuming role defaults count as a declared scope — they do not.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/8fc8dda201bcdc34. Report an issue: GitHub.