diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fa732a492..38773bb9f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT" - name: Upload staged npm package artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: codex-npm-staging path: ${{ steps.stage_npm_package.outputs.pack_output }} diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index 5beadd55c4..26afdaf552 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -350,7 +350,7 @@ jobs: fi fi - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: ${{ matrix.target }} # Upload the per-binary .zst files as well as the new .tar.gz diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 6d3e33450a..95f06a3039 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -1136,27 +1136,38 @@ impl CodexMessageProcessor { ) -> Result<(), JSONRPCErrorError> { // Verify rollout_path is under sessions dir. let rollout_folder = self.config.codex_home.join(codex_core::SESSIONS_SUBDIR); - let canonical_rollout_path = - tokio::fs::canonicalize(rollout_path) - .await - .map_err(|_| JSONRPCErrorError { - code: INVALID_REQUEST_ERROR_CODE, + + let canonical_sessions_dir = match tokio::fs::canonicalize(&rollout_folder).await { + Ok(path) => path, + Err(err) => { + let error = JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, message: format!( - "rollout path `{}` must be in sessions directory", - rollout_path.display() + "failed to archive conversation: unable to resolve sessions directory: {err}" ), data: None, - })?; - if !canonical_rollout_path.starts_with(&rollout_folder) { - return Err(JSONRPCErrorError { + }; + self.outgoing.send_error(request_id, error).await; + return; + } + }; + let canonical_rollout_path = tokio::fs::canonicalize(rollout_path).await; + let canonical_rollout_path = if let Ok(path) = canonical_rollout_path + && path.starts_with(&canonical_sessions_dir) + { + path + } else { + let error = JSONRPCErrorError { code: INVALID_REQUEST_ERROR_CODE, message: format!( "rollout path `{}` must be in sessions directory", rollout_path.display() ), data: None, - }); - } + }; + self.outgoing.send_error(request_id, error).await; + return; + }; // Verify file name matches conversation id. let required_suffix = format!("{conversation_id}.jsonl"); @@ -1184,10 +1195,6 @@ impl CodexMessageProcessor { }); } - // Proactively cancel any listeners for this conversation so our - // shutdown wait does not race with listener tasks consuming events. - self.cancel_conversation_listeners(conversation_id); - // If the conversation is active, request shutdown and wait briefly. if let Some(conversation) = self .conversation_manager @@ -1813,6 +1820,7 @@ impl CodexMessageProcessor { conversation_id, rollout_path, } = params; + match self .archive_conversation_common(conversation_id, &rollout_path) .await @@ -2116,17 +2124,6 @@ impl CodexMessageProcessor { Ok(subscription_id) } - fn cancel_conversation_listeners(&mut self, conversation_id: ConversationId) { - if let Some(ids) = self.conversation_listener_index.remove(&conversation_id) { - for id in ids { - if let Some(sender) = self.conversation_listeners.remove(&id) { - let _ = sender.send(()); - } - self.subscription_to_conversation.remove(&id); - } - } - } - async fn remove_conversation_listener( &mut self, request_id: RequestId, diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 2085c39f02..9dfa3a1316 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -931,8 +931,10 @@ async fn stream_from_fixture( fn rate_limit_regex() -> &'static Regex { static RE: OnceLock = OnceLock::new(); + // Match both OpenAI-style messages like "Please try again in 1.898s" + // and Azure OpenAI-style messages like "Try again in 35 seconds". #[expect(clippy::unwrap_used)] - RE.get_or_init(|| Regex::new(r"Please try again in (\d+(?:\.\d+)?)(s|ms)").unwrap()) + RE.get_or_init(|| Regex::new(r"(?i)try again in\s*(\d+(?:\.\d+)?)\s*(s|ms|seconds?)").unwrap()) } fn try_parse_retry_after(err: &Error) -> Option { @@ -940,7 +942,8 @@ fn try_parse_retry_after(err: &Error) -> Option { return None; } - // parse the Please try again in 1.898s format using regex + // parse retry hints like "try again in 1.898s" or + // "Try again in 35 seconds" using regex let re = rate_limit_regex(); if let Some(message) = &err.message && let Some(captures) = re.captures(message) @@ -950,9 +953,9 @@ fn try_parse_retry_after(err: &Error) -> Option { if let (Some(value), Some(unit)) = (seconds, unit) { let value = value.as_str().parse::().ok()?; - let unit = unit.as_str(); + let unit = unit.as_str().to_ascii_lowercase(); - if unit == "s" { + if unit == "s" || unit.starts_with("second") { return Some(Duration::from_secs_f64(value)); } else if unit == "ms" { return Some(Duration::from_millis(value as u64)); @@ -1427,6 +1430,19 @@ mod tests { assert_eq!(delay, Some(Duration::from_secs_f64(1.898))); } + #[test] + fn test_try_parse_retry_after_azure() { + let err = Error { + r#type: None, + message: Some("Rate limit exceeded. Try again in 35 seconds.".to_string()), + code: Some("rate_limit_exceeded".to_string()), + plan_type: None, + resets_at: None, + }; + let delay = try_parse_retry_after(&err); + assert_eq!(delay, Some(Duration::from_secs(35))); + } + #[test] fn error_response_deserializes_schema_known_plan_type_and_serializes_back() { use crate::token_data::KnownPlan; diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index be1daf483a..6468327543 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -255,7 +255,7 @@ impl std::fmt::Display for UsageLimitReachedError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let message = match self.plan_type.as_ref() { Some(PlanType::Known(KnownPlan::Plus)) => format!( - "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit chatgpt.com/codex/settings/usage to purchase more credits{}", + "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit https://chatgpt.com/codex/settings/usage to purchase more credits{}", retry_suffix_after_or(self.resets_at.as_ref()) ), Some(PlanType::Known(KnownPlan::Team)) | Some(PlanType::Known(KnownPlan::Business)) => { @@ -269,7 +269,7 @@ impl std::fmt::Display for UsageLimitReachedError { .to_string() } Some(PlanType::Known(KnownPlan::Pro)) => format!( - "You've hit your usage limit. Visit chatgpt.com/codex/settings/usage to purchase more credits{}", + "You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits{}", retry_suffix_after_or(self.resets_at.as_ref()) ), Some(PlanType::Known(KnownPlan::Enterprise)) @@ -460,7 +460,7 @@ mod tests { }; assert_eq!( err.to_string(), - "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit chatgpt.com/codex/settings/usage to purchase more credits or try again later." + "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again later." ); } @@ -613,7 +613,7 @@ mod tests { rate_limits: Some(rate_limit_snapshot()), }; let expected = format!( - "You've hit your usage limit. Visit chatgpt.com/codex/settings/usage to purchase more credits or try again at {expected_time}." + "You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at {expected_time}." ); assert_eq!(err.to_string(), expected); }); @@ -647,7 +647,7 @@ mod tests { rate_limits: Some(rate_limit_snapshot()), }; let expected = format!( - "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit chatgpt.com/codex/settings/usage to purchase more credits or try again at {expected_time}." + "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at {expected_time}." ); assert_eq!(err.to_string(), expected); }); diff --git a/codex-rs/core/src/features/legacy.rs b/codex-rs/core/src/features/legacy.rs index acf96a15b6..de3c007bac 100644 --- a/codex-rs/core/src/features/legacy.rs +++ b/codex-rs/core/src/features/legacy.rs @@ -123,7 +123,7 @@ fn set_if_some( if let Some(enabled) = maybe_value { set_feature(features, feature, enabled); log_alias(alias_key, feature); - features.record_legacy_usage_force(alias_key, feature); + features.record_legacy_usage(alias_key, feature); } } diff --git a/codex-rs/core/src/sandboxing/assessment.rs b/codex-rs/core/src/sandboxing/assessment.rs index c7310c1f13..31e76777bb 100644 --- a/codex-rs/core/src/sandboxing/assessment.rs +++ b/codex-rs/core/src/sandboxing/assessment.rs @@ -25,16 +25,6 @@ use tracing::warn; const SANDBOX_ASSESSMENT_TIMEOUT: Duration = Duration::from_secs(5); -const SANDBOX_RISK_CATEGORY_VALUES: &[&str] = &[ - "data_deletion", - "data_exfiltration", - "privilege_escalation", - "system_modification", - "network_access", - "resource_exhaustion", - "compliance", -]; - #[derive(Template)] #[template(path = "sandboxing/assessment_prompt.md", escape = "none")] struct SandboxAssessmentPromptTemplate<'a> { @@ -176,27 +166,26 @@ pub(crate) async fn assess_command( call_id, "success", Some(assessment.risk_level), - &assessment.risk_categories, duration, ); return Some(assessment); } Err(err) => { warn!("failed to parse sandbox assessment JSON: {err}"); - parent_otel.sandbox_assessment(call_id, "parse_error", None, &[], duration); + parent_otel.sandbox_assessment(call_id, "parse_error", None, duration); } }, Ok(Ok(None)) => { warn!("sandbox assessment response did not include any message"); - parent_otel.sandbox_assessment(call_id, "no_output", None, &[], duration); + parent_otel.sandbox_assessment(call_id, "no_output", None, duration); } Ok(Err(err)) => { warn!("sandbox assessment failed: {err}"); - parent_otel.sandbox_assessment(call_id, "model_error", None, &[], duration); + parent_otel.sandbox_assessment(call_id, "model_error", None, duration); } Err(_) => { warn!("sandbox assessment timed out"); - parent_otel.sandbox_assessment(call_id, "timeout", None, &[], duration); + parent_otel.sandbox_assessment(call_id, "timeout", None, duration); } } @@ -229,7 +218,7 @@ fn sandbox_roots_for_prompt(policy: &SandboxPolicy, cwd: &Path) -> Vec fn sandbox_assessment_schema() -> serde_json::Value { json!({ "type": "object", - "required": ["description", "risk_level", "risk_categories"], + "required": ["description", "risk_level"], "properties": { "description": { "type": "string", @@ -240,13 +229,6 @@ fn sandbox_assessment_schema() -> serde_json::Value { "type": "string", "enum": ["low", "medium", "high"] }, - "risk_categories": { - "type": "array", - "items": { - "type": "string", - "enum": SANDBOX_RISK_CATEGORY_VALUES - } - } }, "additionalProperties": false }) diff --git a/codex-rs/core/src/tools/orchestrator.rs b/codex-rs/core/src/tools/orchestrator.rs index f334c4ef55..878e48e8be 100644 --- a/codex-rs/core/src/tools/orchestrator.rs +++ b/codex-rs/core/src/tools/orchestrator.rs @@ -54,12 +54,21 @@ impl ToolOrchestrator { let mut already_approved = false; if needs_initial_approval { + let mut risk = None; + + if let Some(metadata) = req.sandbox_retry_data() { + risk = tool_ctx + .session + .assess_sandbox_command(turn_ctx, &tool_ctx.call_id, &metadata.command, None) + .await; + } + let approval_ctx = ApprovalCtx { session: tool_ctx.session, turn: turn_ctx, call_id: &tool_ctx.call_id, retry_reason: None, - risk: None, + risk, }; let decision = tool.start_approval_async(req, approval_ctx).await; diff --git a/codex-rs/core/templates/sandboxing/assessment_prompt.md b/codex-rs/core/templates/sandboxing/assessment_prompt.md index 7e5664a0ad..dc59f47d7c 100644 --- a/codex-rs/core/templates/sandboxing/assessment_prompt.md +++ b/codex-rs/core/templates/sandboxing/assessment_prompt.md @@ -1,13 +1,10 @@ -You are a security analyst evaluating shell commands that were blocked by a sandbox. Given the provided metadata, summarize the command's likely intent and assess the risk. Return strictly valid JSON with the keys: -- description (concise summary, at most two sentences) +You are a security analyst evaluating shell commands that were blocked by a sandbox. Given the provided metadata, summarize the command's likely intent and assess the risk to help the user decide whether to approve command execution. Return strictly valid JSON with the keys: +- description (concise summary of command intent and potential effects, no more than one sentence, use present tense) - risk_level ("low", "medium", or "high") -- risk_categories (optional array of zero or more category strings) Risk level examples: -- low: read-only inspections, listing files, printing configuration -- medium: modifying project files, installing dependencies, fetching artifacts from trusted sources +- low: read-only inspections, listing files, printing configuration, fetching artifacts from trusted sources +- medium: modifying project files, installing dependencies - high: deleting or overwriting data, exfiltrating secrets, escalating privileges, or disabling security controls -Recognized risk_categories: data_deletion, data_exfiltration, privilege_escalation, system_modification, network_access, resource_exhaustion, compliance. -Use multiple categories when appropriate. If information is insufficient, choose the most cautious risk level supported by the evidence. Respond with JSON only, without markdown code fences or extra commentary. diff --git a/codex-rs/otel/src/otel_event_manager.rs b/codex-rs/otel/src/otel_event_manager.rs index 4006df17d9..5d9cbd4997 100644 --- a/codex-rs/otel/src/otel_event_manager.rs +++ b/codex-rs/otel/src/otel_event_manager.rs @@ -8,7 +8,6 @@ use codex_protocol::models::ResponseItem; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::SandboxPolicy; -use codex_protocol::protocol::SandboxRiskCategory; use codex_protocol::protocol::SandboxRiskLevel; use codex_protocol::user_input::UserInput; use eventsource_stream::Event as StreamEvent; @@ -373,19 +372,9 @@ impl OtelEventManager { call_id: &str, status: &str, risk_level: Option, - risk_categories: &[SandboxRiskCategory], duration: Duration, ) { let level = risk_level.map(|level| level.as_str()); - let categories = if risk_categories.is_empty() { - String::new() - } else { - risk_categories - .iter() - .map(SandboxRiskCategory::as_str) - .collect::>() - .join(", ") - }; tracing::event!( tracing::Level::INFO, @@ -402,7 +391,6 @@ impl OtelEventManager { call_id = %call_id, status = %status, risk_level = level, - risk_categories = categories, duration_ms = %duration.as_millis(), ); } diff --git a/codex-rs/protocol/src/approvals.rs b/codex-rs/protocol/src/approvals.rs index d608dba639..3227ddd1a3 100644 --- a/codex-rs/protocol/src/approvals.rs +++ b/codex-rs/protocol/src/approvals.rs @@ -16,24 +16,10 @@ pub enum SandboxRiskLevel { High, } -#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash, JsonSchema, TS)] -#[serde(rename_all = "snake_case")] -pub enum SandboxRiskCategory { - DataDeletion, - DataExfiltration, - PrivilegeEscalation, - SystemModification, - NetworkAccess, - ResourceExhaustion, - Compliance, -} - #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)] pub struct SandboxCommandAssessment { pub description: String, pub risk_level: SandboxRiskLevel, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub risk_categories: Vec, } impl SandboxRiskLevel { @@ -46,20 +32,6 @@ impl SandboxRiskLevel { } } -impl SandboxRiskCategory { - pub fn as_str(&self) -> &'static str { - match self { - Self::DataDeletion => "data_deletion", - Self::DataExfiltration => "data_exfiltration", - Self::PrivilegeEscalation => "privilege_escalation", - Self::SystemModification => "system_modification", - Self::NetworkAccess => "network_access", - Self::ResourceExhaustion => "resource_exhaustion", - Self::Compliance => "compliance", - } - } -} - #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)] pub struct ExecApprovalRequestEvent { /// Identifier for the associated exec call, if available. diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 413a3aeaa2..c30ba42b56 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -37,7 +37,6 @@ use ts_rs::TS; pub use crate::approvals::ApplyPatchApprovalRequestEvent; pub use crate::approvals::ExecApprovalRequestEvent; pub use crate::approvals::SandboxCommandAssessment; -pub use crate::approvals::SandboxRiskCategory; pub use crate::approvals::SandboxRiskLevel; /// Open/close tags for special user-input blocks. Used across crates to avoid diff --git a/codex-rs/tui/src/bottom_pane/approval_overlay.rs b/codex-rs/tui/src/bottom_pane/approval_overlay.rs index ba36870005..140b5386c4 100644 --- a/codex-rs/tui/src/bottom_pane/approval_overlay.rs +++ b/codex-rs/tui/src/bottom_pane/approval_overlay.rs @@ -20,7 +20,6 @@ use codex_core::protocol::FileChange; use codex_core::protocol::Op; use codex_core::protocol::ReviewDecision; use codex_core::protocol::SandboxCommandAssessment; -use codex_core::protocol::SandboxRiskCategory; use codex_core::protocol::SandboxRiskLevel; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; @@ -356,35 +355,11 @@ fn render_risk_lines(risk: &SandboxCommandAssessment) -> Vec> { ])); } - let mut spans: Vec> = vec!["Risk: ".into(), level_span]; - if !risk.risk_categories.is_empty() { - spans.push(" (".into()); - for (idx, category) in risk.risk_categories.iter().enumerate() { - if idx > 0 { - spans.push(", ".into()); - } - spans.push(risk_category_label(*category).into()); - } - spans.push(")".into()); - } - - lines.push(Line::from(spans)); + lines.push(vec!["Risk: ".into(), level_span].into()); lines.push(Line::from("")); lines } -fn risk_category_label(category: SandboxRiskCategory) -> &'static str { - match category { - SandboxRiskCategory::DataDeletion => "data deletion", - SandboxRiskCategory::DataExfiltration => "data exfiltration", - SandboxRiskCategory::PrivilegeEscalation => "privilege escalation", - SandboxRiskCategory::SystemModification => "system modification", - SandboxRiskCategory::NetworkAccess => "network access", - SandboxRiskCategory::ResourceExhaustion => "resource exhaustion", - SandboxRiskCategory::Compliance => "compliance", - } -} - #[derive(Clone)] enum ApprovalVariant { Exec { id: String, command: Vec }, diff --git a/codex-rs/tui/src/onboarding/auth.rs b/codex-rs/tui/src/onboarding/auth.rs index 56527ac8ef..06e1c629e3 100644 --- a/codex-rs/tui/src/onboarding/auth.rs +++ b/codex-rs/tui/src/onboarding/auth.rs @@ -10,6 +10,7 @@ use codex_login::ShutdownHandle; use codex_login::run_login_server; use crossterm::event::KeyCode; use crossterm::event::KeyEvent; +use crossterm::event::KeyEventKind; use crossterm::event::KeyModifiers; use ratatui::buffer::Buffer; use ratatui::layout::Constraint; @@ -428,7 +429,9 @@ impl AuthModeWidget { should_request_frame = true; } KeyCode::Char(c) - if !key_event.modifiers.contains(KeyModifiers::CONTROL) + if key_event.kind == KeyEventKind::Press + && !key_event.modifiers.contains(KeyModifiers::SUPER) + && !key_event.modifiers.contains(KeyModifiers::CONTROL) && !key_event.modifiers.contains(KeyModifiers::ALT) => { if state.prepopulated_from_env { diff --git a/codex-rs/tui/src/status/card.rs b/codex-rs/tui/src/status/card.rs index fdf6629a0a..11b723fef9 100644 --- a/codex-rs/tui/src/status/card.rs +++ b/codex-rs/tui/src/status/card.rs @@ -313,7 +313,9 @@ impl HistoryCell for StatusHistoryCell { let note_first_line = Line::from(vec![ Span::from("Visit ").cyan(), - "chatgpt.com/codex/settings/usage".cyan().underlined(), + "https://chatgpt.com/codex/settings/usage" + .cyan() + .underlined(), Span::from(" for up-to-date").cyan(), ]); let note_second_line = Line::from(vec![ diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_monthly_limit.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_monthly_limit.snap index cf45f3f810..85e6356c34 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_monthly_limit.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_monthly_limit.snap @@ -7,7 +7,7 @@ expression: sanitized ╭────────────────────────────────────────────────────────────────────────────╮ │ >_ OpenAI Codex (v0.0.0) │ │ │ -│ Visit chatgpt.com/codex/settings/usage for up-to-date │ +│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ │ information on rate limits and credits │ │ │ │ Model: gpt-5-codex (reasoning none, summaries auto) │ diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_reasoning_details.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_reasoning_details.snap index e38a1c7c1b..0b7b74d745 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_reasoning_details.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_includes_reasoning_details.snap @@ -7,7 +7,7 @@ expression: sanitized ╭─────────────────────────────────────────────────────────────────────╮ │ >_ OpenAI Codex (v0.0.0) │ │ │ -│ Visit chatgpt.com/codex/settings/usage for up-to-date │ +│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ │ information on rate limits and credits │ │ │ │ Model: gpt-5-codex (reasoning high, summaries detailed) │ diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_empty_limits_message.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_empty_limits_message.snap index 82ed8fc0b9..17862db238 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_empty_limits_message.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_empty_limits_message.snap @@ -7,7 +7,7 @@ expression: sanitized ╭─────────────────────────────────────────────────────────────────╮ │ >_ OpenAI Codex (v0.0.0) │ │ │ -│ Visit chatgpt.com/codex/settings/usage for up-to-date │ +│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ │ information on rate limits and credits │ │ │ │ Model: gpt-5-codex (reasoning none, summaries auto) │ diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_missing_limits_message.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_missing_limits_message.snap index 82ed8fc0b9..17862db238 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_missing_limits_message.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_missing_limits_message.snap @@ -7,7 +7,7 @@ expression: sanitized ╭─────────────────────────────────────────────────────────────────╮ │ >_ OpenAI Codex (v0.0.0) │ │ │ -│ Visit chatgpt.com/codex/settings/usage for up-to-date │ +│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ │ information on rate limits and credits │ │ │ │ Model: gpt-5-codex (reasoning none, summaries auto) │ diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_stale_limits_message.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_stale_limits_message.snap index bd18719700..2fc0d88744 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_stale_limits_message.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_shows_stale_limits_message.snap @@ -7,7 +7,7 @@ expression: sanitized ╭─────────────────────────────────────────────────────────────────────╮ │ >_ OpenAI Codex (v0.0.0) │ │ │ -│ Visit chatgpt.com/codex/settings/usage for up-to-date │ +│ Visit https://chatgpt.com/codex/settings/usage for up-to-date │ │ information on rate limits and credits │ │ │ │ Model: gpt-5-codex (reasoning none, summaries auto) │ diff --git a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_truncates_in_narrow_terminal.snap b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_truncates_in_narrow_terminal.snap index 64e9271df7..d86e43a458 100644 --- a/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_truncates_in_narrow_terminal.snap +++ b/codex-rs/tui/src/status/snapshots/codex_tui__status__tests__status_snapshot_truncates_in_narrow_terminal.snap @@ -1,5 +1,6 @@ --- source: tui/src/status/tests.rs +assertion_line: 257 expression: sanitized --- /status @@ -7,8 +8,8 @@ expression: sanitized ╭────────────────────────────────────────────╮ │ >_ OpenAI Codex (v0.0.0) │ │ │ -│ Visit chatgpt.com/codex/settings/usage for │ -│ up-to-date │ +│ Visit https://chatgpt.com/codex/settings/ │ +│ usage for up-to-date │ │ information on rate limits and credits │ │ │ │ Model: gpt-5-codex (reasoning │