Hmbown/CodeWhale · error

canonical workspace

Error message

canonical workspace

What it means

Panic from `.expect("canonical workspace")` on `tmp.path().canonicalize()` in the subagent read-only-role test (crates/tui/src/tools/subagent/tests.rs:8366). `canonicalize` resolves the path to an absolute one with symlinks resolved (e.g. macOS `/var` → `/private/var`); it fails with an io::Error when the path does not exist or cannot be resolved. The test needs the canonical form so `git -C <abs-path>` works even when the repo is not the process cwd.

Solutions

  1. Re-run with a stable TMPDIR you created yourself: `TMPDIR=$(mktemp -d) cargo test read_only_inspection_roles`.
  2. Confirm no tmp-cleaner deleted the directory mid-run; disable tmp pruning for the CI session.
  3. If reproducible locally, verify `tmp.path()` still exists immediately after `tempdir()` — if not, the failure is in temp creation, not canonicalization.
  4. As a fixture hardening step, canonicalize before `init_claim_repo` and panic with the io::Error chain so the OS cause is visible.

Example fix

// before
let workspace = tmp.path().canonicalize().expect("canonical workspace");
// after
let workspace = tmp.path().canonicalize()
    .with_context(|| format!("canonicalize {:?}", tmp.path()))
    .expect("canonical workspace");
Defensive patterns

Strategy: validation

Validate before calling

let raw = tmp.path();
assert!(raw.exists(), "temp path {:?} vanished before canonicalize", raw);
let workspace = raw.canonicalize().expect("canonical workspace");

Try / catch

let workspace = tmp.path().canonicalize()
    .with_context(|| format!("canonicalize {:?}", tmp.path()))?;

Prevention

When it happens

Trigger: The temp directory created one line earlier was removed between creation and canonicalization (aggressive tmp cleaners, `TMPDIR` on a flaky mount), or the platform denies path resolution. Also surfaces if `init_claim_repo` inadvertently removes the directory.

Common situations: Running tests in containers with systemd-tmpfiles or `tmpwatch` pruning; sandboxed runners where `/tmp` symlinks cannot be resolved; macOS environments relying on the `/var` → `/private/var` canonicalization with restrictive TCC settings.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/f2a880de67387f5e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tools/subagent/tests.rs:8366

    assert!(
        !registry.envelope_permits("bash", &touch),
        "mutating command must stay refused by the scout envelope"
    );
    assert!(
        !registry.envelope_permits("Bash", &git_log),
        "legacy `Bash` is not the carve-out name; the envelope must keep refusing it"
    );
}

/// #5595: catalog admission, role posture, and the execution envelope are not
/// enough. The concrete read-only executor must accept the canonical Git shape
/// agents use when the repository is not their process cwd. This is the exact
/// end-to-end gap from the v0.9.11 dogfood failure.
#[tokio::test]
async fn read_only_inspection_roles_execute_pwd_and_absolute_git_log() {
    let tmp = tempdir().expect("tempdir");
    init_claim_repo(tmp.path());
    let workspace = tmp.path().canonicalize().expect("canonical workspace");
    let git_log = format!("git -C {} log --oneline -20", workspace.to_string_lossy());

    for role in [FleetRole::Scout, FleetRole::Reviewer, FleetRole::Planner] {
        let mut runtime =
            stub_runtime().with_agent_tool_surface_options(enabled_agent_surface_options());
        runtime.context = ToolContext::new(workspace.clone());
        runtime.worker_profile = WorkerRuntimeProfile::for_role(role.clone());
        seed_read_only_role_deny_list(&mut runtime);
        let registry = SubAgentToolRegistry::new(
            runtime,
            role.clone(),
            None,
            crate::tools::todo::new_shared_todo_list(),
            crate::tools::plan::new_shared_plan_state(),
        );

        for input in [
            json!({"command": "pwd"}),

View on GitHub (pinned to 73e0f67d83)