{"record":{"id":"e8e4d96fcc99fe94","repo":"Hmbown/CodeWhale","slug":"planner-must-dispatch-a-bounded-read","errorCode":null,"errorMessage":"planner must dispatch a bounded read","messagePattern":"planner must dispatch a bounded read","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/tests.rs","lineNumber":8596,"sourceCode":"    }\n    for command in [\"rm -rf crates\", \"git push origin main\", \"bash -lc 'id'\"] {\n        assert!(\n            registry\n                .envelope_refusal(\"bash\", &json!({\"command\": command}))\n                .is_some(),\n            \"planner must refuse {command}\"\n        );\n    }\n    let sentinel = \"PLANNER_PROBE_SENTINEL\";\n    std::fs::write(tmp.path().join(\"sentinel.txt\"), sentinel).expect(\"sentinel\");\n    let output = registry\n        .execute(\n            \"agent_planner\",\n            \"bash\",\n            json!({\"command\": \"cat sentinel.txt\"}),\n        )\n        .await\n        .expect(\"planner must dispatch a bounded read\");\n    assert_eq!(output, sentinel);\n}\n\n#[tokio::test]\nasync fn scout_shell_respects_parent_shell_and_network_ceilings() {\n    let tmp = tempdir().expect(\"tempdir\");\n    let mut shell_off =\n        stub_runtime().with_agent_tool_surface_options(enabled_agent_surface_options());\n    shell_off.context = ToolContext::new(tmp.path().to_path_buf());\n    shell_off.allow_shell = false;\n    shell_off.worker_profile = WorkerRuntimeProfile::for_role(FleetRole::Scout);\n    let shell_off = SubAgentToolRegistry::new(\n        shell_off,\n        FleetRole::Scout,\n        None,\n        crate::tools::todo::new_shared_todo_list(),\n        crate::tools::plan::new_shared_plan_state(),\n    );","sourceCodeStart":8578,"sourceCodeEnd":8614,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/tools/subagent/tests.rs#L8578-L8614","documentation":"This panic is `.expect(\"planner must dispatch a bounded read\")` on `registry.execute(\"agent_planner\", \"bash\", json!({\"command\": \"cat sentinel.txt\"}))` in crates/tui/src/tools/subagent/tests.rs:8596. It asserts that the planner role, after refusing dangerous commands, still allows a proven read-only bash probe whose output must equal the sentinel. If the planner's bash policy rejects even the bounded read (or the command output differs), the expect panics.","triggerScenarios":"Executing a benign `cat sentinel.txt` through an `agent_planner` registry whose bash allow/deny policy misclassifies the command as unbounded, when `allow_shell` is false for the planner profile, or when the sentinel file was not created at tmp.path().","commonSituations":"Tightening the planner bash ceiling so it drops previously allowed read probes, forgetting to seed the sentinel file, or running with allow_shell disabled globally instead of per-role.","solutions":["Confirm the planner's bash policy explicitly permits bounded read commands (cat of files within the workspace context).","Ensure `allow_shell` is true for the planner runtime profile even though other roles (Scout) may have it off.","Verify the sentinel file exists (fixture write at line ~8588 succeeded) and that registry.context points at the same tmp path.","Log the refusal envelope (envelope_refusal) to see which rule rejected the command and adjust the policy, not the test."],"exampleFix":"// before: planner bash ceiling blocks every bash invocation\nif !command.starts_with(\"git \") { return refuse(\"bash\", \"not allowed\"); }\n// after: allow bounded read-only probes\nif is_bounded_read(command) { return execute_read(command); }\nif !command.starts_with(\"git \") { return refuse(\"bash\", \"not allowed\"); }","handlingStrategy":"validation","validationCode":"// pre-check the planner policy before dispatching:\nfn allows_bounded_read(policy: &PlannerBashPolicy, cmd: &str) -> bool {\n    policy.is_bounded_read(cmd)\n}\nassert!(allows_bounded_read(&policy, \"cat sentinel.txt\"),\n    \"planner policy must permit bounded read probes\");","typeGuard":"fn is_bounded_read(cmd: &str) -> bool {\n    let (bin, _) = cmd.split_once(' ').unwrap_or((cmd, \"\"));\n    matches!(bin, \"cat\" | \"head\" | \"tail\" | \"ls\")\n}","tryCatchPattern":"if let Some(refusal) = registry.envelope_refusal(\"bash\", &payload) {\n    panic!(\"bounded read unexpectedly refused: {refusal:?} — inspect planner bash ceiling\");\n}\nlet output = registry.execute(\"agent_planner\", \"bash\", payload).await\n    .unwrap_or_else(|e| panic!(\"bounded read dispatch failed: {e:?}\"));","preventionTips":["Keep an explicit allowlist of bounded read commands in the planner policy rather than a blanket bash ban.","Add a canary assertion that the sentinel fixture exists before dispatching reads.","When editing role ceilings, re-run the planner dispatch test to catch over-narrowing.","Set allow_shell per role, not globally, so planner probes survive scout restrictions."],"tags":["rust","permissions","planner","bash-policy"],"backgroundTag":"permission-denied","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}