From 3779b52e2d2925eaf84c0c9ec57d34a113606db1 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Fri, 6 Feb 2026 23:26:44 -0800 Subject: [PATCH 1/4] Do not poll for usage when using API Key auth (#10973) Fixes #10869 - Gate TUI rate-limit polling on ChatGPT-auth providers only. - `prefetch_rate_limits()` now checks `should_prefetch_rate_limits()`. - New gate requires: - `config.model_provider.requires_openai_auth` - cached auth is ChatGPT (`CodexAuth::is_chatgpt_auth`) - Prevents `/wham/usage` polling in API/custom-endpoint profiles. --- codex-rs/tui/src/chatwidget.rs | 18 ++++++++++++------ codex-rs/tui/src/chatwidget/tests.rs | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 94fece3620..f103f5bfc8 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -4492,12 +4492,7 @@ impl ChatWidget { fn prefetch_rate_limits(&mut self) { self.stop_rate_limit_poller(); - if !self - .auth_manager - .auth_cached() - .as_ref() - .is_some_and(CodexAuth::is_chatgpt_auth) - { + if !self.should_prefetch_rate_limits() { return; } @@ -4522,6 +4517,17 @@ impl ChatWidget { self.rate_limit_poller = Some(handle); } + fn should_prefetch_rate_limits(&self) -> bool { + if !self.config.model_provider.requires_openai_auth { + return false; + } + + self.auth_manager + .auth_cached() + .as_ref() + .is_some_and(CodexAuth::is_chatgpt_auth) + } + fn lower_cost_preset(&self) -> Option { let models = self.models_manager.try_list_models(&self.config).ok()?; models diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index fcd2b84616..bf173ceca3 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1138,6 +1138,22 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) { )); } +#[tokio::test] +async fn prefetch_rate_limits_is_gated_on_chatgpt_auth_provider() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; + + assert!(!chat.should_prefetch_rate_limits()); + + set_chatgpt_auth(&mut chat); + assert!(chat.should_prefetch_rate_limits()); + + chat.config.model_provider.requires_openai_auth = false; + assert!(!chat.should_prefetch_rate_limits()); + + chat.prefetch_rate_limits(); + assert!(chat.rate_limit_poller.is_none()); +} + #[tokio::test] async fn worked_elapsed_from_resets_when_timer_restarts() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; From f3f35526a8056e28a99ef57136ef548301eee9d2 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Fri, 6 Feb 2026 23:41:08 -0800 Subject: [PATCH 2/4] Show left/right arrows to navigate in tui request_user_input (#10921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot 2026-02-06 at 10 25 13 AM Screenshot 2026-02-06 at 10 26 37 AM "left/right to navigate questions" in request_user_input footer --- .../src/bottom_pane/request_user_input/mod.rs | 115 +++++++++++++++++- ...tests__request_user_input_footer_wrap.snap | 3 +- ...quest_user_input_multi_question_first.snap | 3 +- ...equest_user_input_multi_question_last.snap | 3 +- 4 files changed, 117 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs index 9542772446..a00aedb0f1 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs +++ b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs @@ -443,10 +443,10 @@ impl RequestUserInputOverlay { }; tips.push(enter_tip); if question_count > 1 { - if is_last_question { - tips.push(FooterTip::new("ctrl + n first question")); - } else { - tips.push(FooterTip::new("ctrl + n next question")); + if self.has_options() && !self.focus_is_notes() { + tips.push(FooterTip::new("←/→ to navigate questions")); + } else if !self.has_options() { + tips.push(FooterTip::new("ctrl + p / ctrl + n change question")); } } if !(self.has_options() && notes_visible) { @@ -1042,6 +1042,14 @@ impl BottomPaneView for RequestUserInputOverlay { self.move_question(false); return; } + KeyEvent { + code: KeyCode::Left, + modifiers: KeyModifiers::NONE, + .. + } if self.has_options() && matches!(self.focus, Focus::Options) => { + self.move_question(false); + return; + } KeyEvent { code: KeyCode::Char('l'), modifiers: KeyModifiers::NONE, @@ -1050,6 +1058,14 @@ impl BottomPaneView for RequestUserInputOverlay { self.move_question(true); return; } + KeyEvent { + code: KeyCode::Right, + modifiers: KeyModifiers::NONE, + .. + } if self.has_options() && matches!(self.focus, Focus::Options) => { + self.move_question(true); + return; + } _ => {} } @@ -1643,6 +1659,97 @@ mod tests { assert_eq!(overlay.current_index(), 0); } + #[test] + fn left_right_move_between_questions_in_options() { + let (tx, _rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event( + "turn-1", + vec![ + question_with_options("q1", "Pick one"), + question_with_options("q2", "Pick two"), + ], + ), + tx, + true, + false, + false, + ); + + assert_eq!(overlay.current_index(), 0); + overlay.handle_key_event(KeyEvent::from(KeyCode::Right)); + assert_eq!(overlay.current_index(), 1); + overlay.handle_key_event(KeyEvent::from(KeyCode::Left)); + assert_eq!(overlay.current_index(), 0); + } + + #[test] + fn options_notes_focus_hides_question_navigation_tip() { + let (tx, _rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event( + "turn-1", + vec![ + question_with_options("q1", "Pick one"), + question_with_options("q2", "Pick two"), + ], + ), + tx, + true, + false, + false, + ); + let tips = overlay.footer_tips(); + let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::>(); + assert_eq!( + tip_texts, + vec![ + "tab to add notes", + "enter to submit answer", + "←/→ to navigate questions", + "esc to interrupt", + ] + ); + + overlay.handle_key_event(KeyEvent::from(KeyCode::Tab)); + let tips = overlay.footer_tips(); + let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::>(); + assert_eq!( + tip_texts, + vec!["tab or esc to clear notes", "enter to submit answer",] + ); + } + + #[test] + fn freeform_shows_ctrl_p_and_ctrl_n_question_navigation_tip() { + let (tx, _rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event( + "turn-1", + vec![ + question_with_options("q1", "Area"), + question_without_options("q2", "Goal"), + ], + ), + tx, + true, + false, + false, + ); + overlay.move_question(true); + + let tips = overlay.footer_tips(); + let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::>(); + assert_eq!( + tip_texts, + vec![ + "enter to submit all", + "ctrl + p / ctrl + n change question", + "esc to interrupt", + ] + ); + } + #[test] fn tab_opens_notes_when_option_selected() { let (tx, _rx) = test_sender(); diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap index 3fd7194648..872bfe1d0e 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2600 expression: "render_snapshot(&overlay, area)" --- @@ -11,4 +12,4 @@ expression: "render_snapshot(&overlay, area)" 3. Option 3 Third choice. tab to add notes | enter to submit answer - ctrl + n next question | esc to interrupt + ←/→ to navigate questions | esc to interrupt diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap index 536e34dbba..bb1c2a726a 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2744 expression: "render_snapshot(&overlay, area)" --- @@ -10,4 +11,4 @@ expression: "render_snapshot(&overlay, area)" 2. Option 2 Second choice. 3. Option 3 Third choice. - tab to add notes | enter to submit answer | ctrl + n next question | esc to interrupt + tab to add notes | enter to submit answer | ←/→ to navigate questions | esc to interrupt diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap index 95507c3358..dbe06d4041 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2770 expression: "render_snapshot(&overlay, area)" --- @@ -12,4 +13,4 @@ expression: "render_snapshot(&overlay, area)" - enter to submit all | ctrl + n first question | esc to interrupt + enter to submit all | ctrl + p / ctrl + n change question | esc to interrupt From 4cd0c42a28dbfdf370ed053373233b9ca2ce7802 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 6 Feb 2026 23:49:19 -0800 Subject: [PATCH 3/4] fix: normalize line endings when reading file on Windows (#10988) I did not wait for CI on https://github.com/openai/codex/pull/10980 because it was blocking an alpha release, but apparently it broken the Windows build. --- codex-rs/core/src/config/schema.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codex-rs/core/src/config/schema.rs b/codex-rs/core/src/config/schema.rs index d5a02c8c6a..95aea130e6 100644 --- a/codex-rs/core/src/config/schema.rs +++ b/codex-rs/core/src/config/schema.rs @@ -138,6 +138,9 @@ Run `just write-config-schema` to overwrite with your changes.\n\n{diff}" write_config_schema(&tmp_path).expect("write config schema to temp path"); let tmp_contents = std::fs::read_to_string(&tmp_path).expect("read back config schema from temp path"); + #[cfg(windows)] + let fixture = fixture.replace("\r\n", "\n"); + assert_eq!( fixture, tmp_contents, "fixture should match exactly with generated schema" From 673a348681d674ed95e0cd74e219711ae632f827 Mon Sep 17 00:00:00 2001 From: Michael Bolin Date: Fri, 6 Feb 2026 23:50:27 -0800 Subject: [PATCH 4/4] fix: remove config.schema.json from tag check --- .github/workflows/rust-release.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index aa92693eee..2886f7c887 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -45,15 +45,6 @@ jobs: echo "✅ Tag and Cargo.toml agree (${tag_ver})" echo "::endgroup::" - - name: Verify config schema fixture - shell: bash - working-directory: codex-rs - run: | - set -euo pipefail - echo "If this fails, run: just write-config-schema to overwrite fixture with intentional changes." - cargo run -p codex-core --bin codex-write-config-schema - git diff --exit-code core/config.schema.json - build: needs: tag-check name: Build - ${{ matrix.runner }} - ${{ matrix.target }}