You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We layered too many permission/review abstractions on top of the mode, so neither users nor developers can predict what will actually happen in a given mode — the mode is supposed to be the contract, but it's been buried under competing layers that override it.
The symptom that surfaced this: in YOLO mode, tool calls still fire [approval] prompts — observed on exec_shell running git push (to a feature branch) and on MCP mutations like mcp_github_create_pull_request. There's also a user report ("agent requires user permission in YOLO after upgrade", 0.8.64).
The contract we want — derive correctness from THIS, not from code comments
The mode selected via Tab is the single, authoritative description of what happens. No secondary layer may override it.
YOLO: everything runs; zero prompts (shell, MCP, network, publish/tag/release).
Plan: read-only + prompts; no writes or execution (planning only).
In every mode the agent may still ask informational questions (request_user_input, e.g. "which option?") — those are not permission gates and must keep working.
Do not trust the comments in the approval / auto-review code. Several assert the opposite of this contract as if it were intended (e.g. "publish-like actions … must still surface a forced prompt [regardless of mode]"). Those describe a drifted design, not the product intent.
Open question to resolve: do explicit "never do X" deny rules survive? If yes, they are hard prohibitions (not prompts) and apply in all modes; if even that muddies "the mode is the contract," consider removing them too. Decide deliberately.
What's wrong today — the layers that contradict the mode
At least these concepts currently participate in a single approval decision, and they can disagree: AppMode (YOLO/Agent/Plan) · internal ApprovalMode (Bypass/Auto/Never/OnFailure…) · auto_approve flag · allow_shell · trust_mode · auto_review policy (block/allow rules + built-in safety_floor + deterministic_fallback) · typed ask-rules (permissions.toml) · approval_force_prompt · workspace trust · a hardcoded MCP read-only list + MCP approval gate · and a second mode check at the UI layer (should_auto_approve_approval_request). That last one is the tell: the engine emits ApprovalRequired and then the UI re-decides whether to honor the mode — so the mode is consulted in two places that can race.
Concrete override sites (verified):
crates/tui/src/tui/auto_review.rs
AutoReviewPolicy::evaluate() (~285): block_rules → safety_floor → allow_rules → deterministic_fallback, with no check that yields to the mode first.
deterministic_fallback() (358): McpAction → HoldForReview ("MCP actions may have remote side effects") unconditionally — why every non-read-only MCP tool prompts even in YOLO.
shell_tokens_are_publish_like() (402): matches classify_command() against "git push" | "gh release" | "npm publish" | "cargo publish" — so anygit push (feature branch or release) becomes Publish.
crates/tui/src/core/engine/turn_loop.rs
auto_review_force_prompt_overrides_auto_approve() (46): forces a prompt past YOLO for decision==hold_for_review && action_kind==publish.
MCP gate (1716–1719): approval_required = !read_only; with noauto_approve check — contrast the shell ask-rule path (1789–1801) which correctly gates on !self.session.auto_approve. MCP is held to a different, mode-blind contract.
ForcePrompt application (1830–1846).
Mode plumbing (correct, for reference): base_policy_for_mode (crates/tui/src/tui/app.rs ~1130) maps Yolo→Bypass; Tab→cycle_mode→sync_mode_update→Op::ChangeMode→apply_runtime_mode_policy. The second mode check is should_auto_approve_approval_request (crates/tui/src/tui/ui.rs ~218).
What to do
Audit first. Map every site that makes a permission/review decision and which "mode-like" signal it consults. Produce a short map of how they interact and where they contradict the mode. This map is the deliverable that makes "what is real" legible again.
Flatten — prioritize deletion over adding more bypasses. Make the mode the only authority for all three modes (not just YOLO). Rip out the auto-review override machinery (safety_floor, the unconditional MCP/destructive holds in deterministic_fallback, auto_review_force_prompt_overrides_auto_approve) and the dormant Auto mode, and defer that entire subsystem to 0.8.67 — re-introduce it cleanly together with Auto when Auto actually ships. (Auto is already deferred from the UI surfaces in 0.8.66 per the CHANGELOG; the dormant machinery is what's still overriding modes.)
Treat MCP identically to shell — the mode governs; remove the separate, mode-blind hardcoded gate.
Collapse or strictly subordinate the derived flags (ApprovalMode, auto_approve, trust_mode) so they can never contradict the mode.
Prior context
PR fix(tui): split auto mode from yolo bypass #3664 ("split auto mode from yolo bypass") stated the intent ("keep YOLO as true Bypass/no-prompt authority") but the implementation drifted — the auto-review hooks above still override it.
CHANGELOG (0.8.66): "Deferred Auto mode from the user-facing mode picker, cycle, hotbar, /mode command, and runtime-thread mode overrides … auto folds back to Agent." So Auto is already hidden; only the override machinery lingers.
There is a local, unpushed branch codex/yolo-bypass-authoritative with a minimal patch (a Bypass short-circuit in evaluate() + an auto_approve check on the MCP gate) that is green and includes two new yolo_bypass_allows_* tests. It is direction (B) — it adds another layer and is not the preferred approach. Use it only as a diagnostic reference for the override sites; the preferred fix is the deletion/flatten above.
Acceptance
A user or developer can predict behavior from the mode alone.
YOLO: no prompts for shell / MCP / publish. Manual repro: /mode yolo, then have the agent push a branch and open a PR.
Agent / Plan: prompts fire exactly as defined above.
request_user_input (informational questions) still works in every mode.
The problem (one sentence)
We layered too many permission/review abstractions on top of the mode, so neither users nor developers can predict what will actually happen in a given mode — the mode is supposed to be the contract, but it's been buried under competing layers that override it.
The symptom that surfaced this: in YOLO mode, tool calls still fire
[approval]prompts — observed onexec_shellrunninggit push(to a feature branch) and on MCP mutations likemcp_github_create_pull_request. There's also a user report ("agent requires user permission in YOLO after upgrade", 0.8.64).The contract we want — derive correctness from THIS, not from code comments
The mode selected via Tab is the single, authoritative description of what happens. No secondary layer may override it.
request_user_input, e.g. "which option?") — those are not permission gates and must keep working.What's wrong today — the layers that contradict the mode
At least these concepts currently participate in a single approval decision, and they can disagree:
AppMode(YOLO/Agent/Plan) · internalApprovalMode(Bypass/Auto/Never/OnFailure…) ·auto_approveflag ·allow_shell·trust_mode·auto_reviewpolicy (block/allow rules + built-insafety_floor+deterministic_fallback) · typed ask-rules (permissions.toml) ·approval_force_prompt· workspace trust · a hardcoded MCP read-only list + MCP approval gate · and a second mode check at the UI layer (should_auto_approve_approval_request). That last one is the tell: the engine emitsApprovalRequiredand then the UI re-decides whether to honor the mode — so the mode is consulted in two places that can race.Concrete override sites (verified):
crates/tui/src/tui/auto_review.rsAutoReviewPolicy::evaluate()(~285): block_rules →safety_floor→ allow_rules →deterministic_fallback, with no check that yields to the mode first.safety_floor()(330):ToolActionKind::Publish→HoldForReviewwith no mode check (the YOLO mode silently approves publish actions (cargo publish / git push --tags), defeating safety_floor durable-review #3735 floor); also a!workspace_trusted && Destructive → AskUserarm with no Bypass check.deterministic_fallback()(358):McpAction→HoldForReview("MCP actions may have remote side effects") unconditionally — why every non-read-only MCP tool prompts even in YOLO.shell_tokens_are_publish_like()(402): matchesclassify_command()against"git push" | "gh release" | "npm publish" | "cargo publish"— so anygit push(feature branch or release) becomesPublish.crates/tui/src/core/engine/turn_loop.rsauto_review_force_prompt_overrides_auto_approve()(46): forces a prompt past YOLO fordecision==hold_for_review && action_kind==publish.approval_required = !read_only;with noauto_approvecheck — contrast the shell ask-rule path (1789–1801) which correctly gates on!self.session.auto_approve. MCP is held to a different, mode-blind contract.base_policy_for_mode(crates/tui/src/tui/app.rs~1130) maps Yolo→Bypass; Tab→cycle_mode→sync_mode_update→Op::ChangeMode→apply_runtime_mode_policy. The second mode check isshould_auto_approve_approval_request(crates/tui/src/tui/ui.rs~218).What to do
safety_floor, the unconditional MCP/destructive holds indeterministic_fallback,auto_review_force_prompt_overrides_auto_approve) and the dormant Auto mode, and defer that entire subsystem to 0.8.67 — re-introduce it cleanly together with Auto when Auto actually ships. (Auto is already deferred from the UI surfaces in 0.8.66 per the CHANGELOG; the dormant machinery is what's still overriding modes.)ApprovalMode,auto_approve,trust_mode) so they can never contradict the mode.Prior context
/modecommand, and runtime-thread mode overrides …autofolds back to Agent." So Auto is already hidden; only the override machinery lingers.codex/yolo-bypass-authoritativewith a minimal patch (aBypassshort-circuit inevaluate()+ anauto_approvecheck on the MCP gate) that is green and includes two newyolo_bypass_allows_*tests. It is direction (B) — it adds another layer and is not the preferred approach. Use it only as a diagnostic reference for the override sites; the preferred fix is the deletion/flatten above.Acceptance
/mode yolo, then have the agent push a branch and open a PR.request_user_input(informational questions) still works in every mode.cargo test -p codewhale-tui --bin codewhale-tui --lockedgreen (update any test that encodes the old YOLO mode silently approves publish actions (cargo publish / git push --tags), defeating safety_floor durable-review #3735 "publish force-prompts in YOLO" behavior — e.g.yolo_mode_forces_prompt_for_publish_like_shell);cargo fmtclean./cc the hotbar PR #3788 is unrelated and should stay separate.