Skip to content

Commit 7a01417

Browse files
committed
file: ultraworkers#159 — run_turn_loop hardcodes empty denied_tools, permission denials absent from multi-turn sessions
1 parent 986f8e8 commit 7a01417

1 file changed

Lines changed: 29 additions & 0 deletions

File tree

ROADMAP.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6075,3 +6075,32 @@ print(len(engine.mutable_messages)) # 3 — silently truncated from 5
60756075
**Blocker.** None.
60766076

60776077
**Source.** Jobdori dogfood sweep 2026-04-22 06:36 KST — probed `query_engine.py` compact path, confirmed no structured compaction signal in `TurnResult` or stream output.
6078+
6079+
## Pinpoint #159. `run_turn_loop` hardcodes empty denied_tools — permission denials silently absent from multi-turn sessions
6080+
6081+
**Gap.** `PortRuntime.run_turn_loop` (`src/runtime.py:163`) calls `engine.submit_message(turn_prompt, command_names, tool_names, ())` with a hardcoded empty tuple for `denied_tools`. By contrast, `bootstrap_session` calls `_infer_permission_denials(matches)` and passes the result. Result: any tool that would be denied (e.g., bash-family tools gated as "destructive") silently appears unblocked across all turns in `turn-loop` mode. The `TurnResult.permission_denials` tuple is always empty for multi-turn runs, giving a false "clean" permission picture to any claw consuming those results.
6082+
6083+
**Repro.**
6084+
```python
6085+
import sys; sys.path.insert(0, 'src')
6086+
from runtime import PortRuntime
6087+
results = PortRuntime().run_turn_loop('run bash ls', max_turns=2)
6088+
for r in results:
6089+
assert r.permission_denials == () # passes — denials never surfaced
6090+
```
6091+
6092+
Compare `bootstrap_session` for the same prompt — it produces a `PermissionDenial` for bash-family tools.
6093+
6094+
**Root cause.** `src/runtime.py:163` — `engine.submit_message(turn_prompt, command_names, tool_names, ())`. The `()` is a hardcoded literal; `_infer_permission_denials` is never called in the turn-loop path.
6095+
6096+
**Fix shape (~5 lines).** Before the turn loop, compute:
6097+
```python
6098+
denials = tuple(self._infer_permission_denials(matches))
6099+
```
6100+
Then pass `denied_tools=denials` to every `submit_message` call inside the loop. Mirrors the existing pattern in `bootstrap_session`.
6101+
6102+
**Acceptance.** `run_turn_loop('run bash ls').permission_denials` is non-empty and matches what `bootstrap_session` returns for the same prompt. Multi-turn session security posture is symmetric with single-turn bootstrap.
6103+
6104+
**Blocker.** None.
6105+
6106+
**Source.** Jobdori dogfood sweep 2026-04-22 06:46 KST — diffed `bootstrap_session` vs `run_turn_loop` in `src/runtime.py`, confirmed asymmetric permission denial propagation.

0 commit comments

Comments
 (0)