fix: prevent ordinary prompt text from being interpreted as a mode switch#3455
fix: prevent ordinary prompt text from being interpreted as a mode switch#3455mvanhorn wants to merge 2 commits into
Conversation
parse_mode's broad catch-all silently coerced any unrecognized string into a valid AppMode, so a stray prompt fragment reaching the runtime turn-start resolver could enter Plan/Agent/YOLO mode with no explicit request (Hmbown#3387). Split out a strict parse_mode_opt that resolves only the exact explicit tokens (agent/plan/yolo plus the numeric aliases 1/2/3, matching the /mode command's parse_mode_arg) and returns None otherwise; parse_mode stays an infallible wrapper defaulting unknown input to Agent. Adds regression coverage asserting prompt fragments like "plan a trip to Tokyo" and "enter yolo mode" never coerce into a mode. Fixes Hmbown#3387 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzUHiFSX6tAEdQtQGA3hDx
Round-2 review: when StartTurnRequest.mode carries a non-token value, fall back to the thread's persisted mode via parse_mode_opt instead of coercing to Agent, so an invalid override never crosses the mode boundary (Hmbown#3387). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzUHiFSX6tAEdQtQGA3hDx
This is awesome - thanks so much Matt!! Been frustrated with this behavior myself and have other attempts at solving this so will fold this in if merging directly doesn't make sense |
Summary
Mode changes were able to leak from ordinary prompt text. On the runtime turn-start path,
parse_moderesolved a mode string with a broad catch-all (_ => AppMode::Agent), so any unrecognized value silently became a valid mode instead of being rejected. Becausestart_turnprefersStartTurnRequest.modeover the thread's persisted mode, a stray prompt-like override (e.g.enter yolo mode) that should be ignored was coerced intoAgent, silently changing a Plan/YOLO thread's mode without an explicit request (#3387).This draws a clear boundary between prompt-submission text and mode-command parsing:
parse_mode_opt(mode) -> Option<AppMode>that resolves only the explicit mode tokens, matching the/modecommand's own accepted set:agent/plan/yoloplus the numeric aliases1/2/3. Anything else returnsNone.parse_modestays an infallible wrapper (parse_mode_opt(..).unwrap_or(AppMode::Agent)), so the persisted/default mode path is unchanged (AppMode::as_setting()always yields a recognized token).start_turncall site, an unrecognized per-turn override now falls back to the thread's persisted mode rather than coercing toAgent, so an invalid override never crosses the mode boundary.Mode transitions continue to originate only from explicit sources: the Tab cycle,
/mode, the mode picker, and config/startup defaults. The composer/submit path (looks_like_slash_command_input, which requires a leading/) was already strict and is left untouched.Why this matters
Root cause: the lone runtime mode resolver treated every non-token string as a valid mode, and the turn-start call preferred the request override unconditionally, so free-form prompt text could silently change approval/sandbox/trust posture.
The failing case now fixed: starting a turn on a Plan or YOLO thread with a request mode such as
enter yolo mode(or any non-token fragment) no longer flips the thread toAgent; the unrecognized override is ignored and the persisted mode is preserved. Regression tests assert that prompt fragments (plan a trip to Tokyo,switch the agent on,enter yolo mode,agent of chaos,mode) never resolve to a mode, while exact tokens and numeric aliases still do.Testing
cargo fmtclean on the changed cratecargo test -p codewhale-tui parse_mode— allparse_mode/parse_mode_optregression tests pass(Note: the full
cargo test --workspacebuild currently fails before reaching these tests due to a pre-existing non-exhaustive match incrates/tui/src/main.rsdoctor_api_key_source_label—ApiKeySource::Command/ApiKeySource::Secretare uncovered onmain, unrelated to this change. Theparse_modetests were verified passing under a local workaround for that build error.)Fixes #3387
Checklist