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
- Re-run with a stable TMPDIR you created yourself: `TMPDIR=$(mktemp -d) cargo test read_only_inspection_roles`.
- Confirm no tmp-cleaner deleted the directory mid-run; disable tmp pruning for the CI session.
- If reproducible locally, verify `tmp.path()` still exists immediately after `tempdir()` — if not, the failure is in temp creation, not canonicalization.
- 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
- Canonicalize immediately after creating the temp dir, before any setup that could remove it.
- Disable aggressive tmp cleaners for the duration of test runs.
- Include the failing path in the panic message for diagnosability.
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
- event recovery chunk length fits u64
- event recovery chunk length fits usize
- Expected Ok, got
- expected resume command
- Expected Warning for relative path argument, got
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)