Compare commits

...

13 Commits

Author SHA1 Message Date
Joe Gershenson
1068a41fcf Stabilize TUI memory mode state test 2026-04-15 13:22:09 -07:00
Joe Gershenson
5976ceb8cc Stabilize app-server CI timeouts 2026-04-15 13:22:09 -07:00
Joe Gershenson
55b2274989 Wait for TUI memory mode persistence 2026-04-15 13:22:09 -07:00
Joe Gershenson
5676b840c2 Fix TUI memory mode test isolation 2026-04-15 13:22:09 -07:00
Joe Gershenson
63230bc9b9 Fix app-server argument lint 2026-04-15 13:22:09 -07:00
Joe Gershenson
b0bea0225b Stabilize remaining Bazel timing flakes 2026-04-15 13:22:09 -07:00
Joe Gershenson
c7d99c63cf Stabilize Bazel test timing under load 2026-04-15 13:22:00 -07:00
Joe Gershenson
a04370f703 Fix multi-agent interrupt test startup wait 2026-04-15 13:21:39 -07:00
Joe Gershenson
a3719d9052 Use NoProfile in PowerShell exec tests 2026-04-15 13:21:39 -07:00
Joe Gershenson
7e47e850da Stabilize multi-agent interrupt test 2026-04-15 13:21:39 -07:00
Joe Gershenson
6a3962f749 Fix marketplace config test and argument lint 2026-04-15 13:21:39 -07:00
Joe Gershenson
d61d3136a6 Stabilize Windows thread unsubscribe sleep test 2026-04-15 13:21:39 -07:00
Joe Gershenson
6703333d2a Fix Windows marketplace local source parsing 2026-04-15 13:21:39 -07:00
64 changed files with 215 additions and 104 deletions

View File

@@ -6,6 +6,7 @@ codex_rust_crate(
integration_test_tags_extra_by_stem = {
"all": ["flaky"],
},
integration_test_args = ["--test-threads=2"],
integration_test_timeout = "long",
test_tags = ["no-sandbox"],
)

View File

@@ -37,6 +37,7 @@ pub use responses::create_final_assistant_message_sse_response;
pub use responses::create_request_permissions_sse_response;
pub use responses::create_request_user_input_sse_response;
pub use responses::create_shell_command_sse_response;
pub use responses::create_shell_command_sse_response_from_command;
pub use rollout::create_fake_rollout;
pub use rollout::create_fake_rollout_with_source;
pub use rollout::create_fake_rollout_with_text_elements;

View File

@@ -10,11 +10,37 @@ pub fn create_shell_command_sse_response(
) -> anyhow::Result<String> {
// The `arguments` for the `shell_command` tool is a serialized JSON object.
let command_str = shlex::try_join(command.iter().map(String::as_str))?;
let tool_call_arguments = serde_json::to_string(&json!({
"command": command_str,
create_shell_command_sse_response_from_command(
&command_str,
workdir,
timeout_ms,
call_id,
/*login*/ None,
)
}
pub fn create_shell_command_sse_response_from_command(
command: &str,
workdir: Option<&Path>,
timeout_ms: Option<u64>,
call_id: &str,
login: Option<bool>,
) -> anyhow::Result<String> {
// Use this when a test already has a shell command string. It fixes string
// quoting for those callers by avoiding a rebuild from argv with POSIX
// rules, which can change how Windows PowerShell parses the command; for
// sleep-based tests, nested parsing can add another PowerShell startup
// before the requested sleep even begins.
let mut tool_call_arguments = json!({
"command": command,
"workdir": workdir.map(|w| w.to_string_lossy()),
"timeout_ms": timeout_ms
}))?;
});
if let Some(login) = login {
tool_call_arguments["login"] = json!(login);
}
let tool_call_arguments = serde_json::to_string(&tool_call_arguments)?;
Ok(responses::sse(vec![
responses::ev_response_created("resp-1"),
responses::ev_function_call(call_id, "shell_command", &tool_call_arguments),

View File

@@ -17,6 +17,7 @@ use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
use pretty_assertions::assert_eq;
use std::path::Path;
use tempfile::TempDir;
use tokio::time::sleep;
use tokio::time::timeout;
use wiremock::Mock;
use wiremock::MockServer;
@@ -24,7 +25,24 @@ use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
async fn wait_for_received_requests(server: &MockServer, expected: usize) -> Result<()> {
timeout(DEFAULT_READ_TIMEOUT, async {
loop {
let received = server
.received_requests()
.await
.map_or(0, |requests| requests.len());
if received >= expected {
break;
}
sleep(std::time::Duration::from_millis(25)).await;
}
})
.await?;
Ok(())
}
fn create_config_toml_custom_provider(
codex_home: &Path,
@@ -373,6 +391,7 @@ async fn get_auth_status_omits_token_after_proactive_refresh_failure() -> Result
)
.await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
wait_for_received_requests(&server, /*expected*/ 1).await?;
let request_id = mcp
.send_get_auth_status_request(GetAuthStatusParams {
@@ -440,6 +459,7 @@ async fn get_auth_status_returns_token_after_proactive_refresh_recovery() -> Res
)
.await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
wait_for_received_requests(&server, /*expected*/ 1).await?;
let failed_request_id = mcp
.send_get_auth_status_request(GetAuthStatusParams {

View File

@@ -15,7 +15,7 @@ use std::path::PathBuf;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const FILENAME_TS: &str = "2025-01-02T12-00-00";
const META_RFC3339: &str = "2025-01-02T12:00:00Z";
const UPDATED_AT_RFC3339: &str = "2025-01-02T12:00:00.000Z";

View File

@@ -11,7 +11,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const SHORT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(500);
const STOP_GRACE_PERIOD: std::time::Duration = std::time::Duration::from_millis(250);
const SESSION_UPDATED_METHOD: &str = "fuzzyFileSearch/sessionUpdated";

View File

@@ -56,7 +56,7 @@ use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn list_apps_returns_empty_when_connectors_disabled() -> Result<()> {

View File

@@ -18,7 +18,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn turn_start_forwards_client_metadata_to_responses_request_v2() -> Result<()> {

View File

@@ -21,7 +21,7 @@ use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
/// Confirms the server returns the default collaboration mode presets in a stable order.
#[tokio::test]

View File

@@ -38,7 +38,7 @@ use std::collections::BTreeMap;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const AUTO_COMPACT_LIMIT: i64 = 1_000;
const COMPACT_PROMPT: &str = "Summarize the conversation.";
const INVALID_REQUEST_ERROR_CODE: i64 = -32600;

View File

@@ -33,7 +33,7 @@ use serde_json::json;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
fn write_config(codex_home: &TempDir, contents: &str) -> Result<()> {
Ok(std::fs::write(

View File

@@ -47,7 +47,7 @@ use tokio_tungstenite::tungstenite::http::HeaderValue;
use tokio_tungstenite::tungstenite::http::header::AUTHORIZATION;
use tokio_tungstenite::tungstenite::http::header::ORIGIN;
pub(super) const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
pub(super) const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(30);
pub(super) type WsClient = WebSocketStream<MaybeTlsStream<tokio::net::TcpStream>>;
type HmacSha256 = Hmac<Sha256>;

View File

@@ -34,7 +34,7 @@ use tempfile::TempDir;
use tokio::time::timeout;
use wiremock::MockServer;
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(30);
/// Ensures dynamic tool specs are serialized into the model request payload.
#[tokio::test]

View File

@@ -24,7 +24,7 @@ use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn mock_experimental_method_requires_experimental_api_capability() -> Result<()> {

View File

@@ -24,7 +24,7 @@ use std::collections::BTreeMap;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn experimental_feature_list_returns_feature_metadata_with_stage() -> Result<()> {

View File

@@ -27,7 +27,7 @@ use std::os::unix::fs::symlink;
#[cfg(unix)]
use std::process::Command;
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(30);
async fn initialized_mcp(codex_home: &TempDir) -> Result<McpProcess> {
let mut mcp = McpProcess::new(codex_home.path()).await?;

View File

@@ -24,7 +24,7 @@ use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn initialize_uses_client_info_name_as_originator() -> Result<()> {

View File

@@ -10,7 +10,7 @@ use tempfile::TempDir;
use tokio::time::Duration;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn marketplace_add_local_directory_source() -> Result<()> {

View File

@@ -34,7 +34,7 @@ use tempfile::TempDir;
use tokio::net::TcpListener;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(30);
const TEST_RESOURCE_URI: &str = "test://codex/resource";
const TEST_BLOB_RESOURCE_URI: &str = "test://codex/resource.bin";
const TEST_RESOURCE_BLOB: &str = "YmluYXJ5LXJlc291cmNl";

View File

@@ -63,7 +63,7 @@ use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const CONNECTOR_ID: &str = "calendar";
const CONNECTOR_NAME: &str = "Calendar";
const TOOL_NAMESPACE: &str = "mcp__codex_apps__calendar";

View File

@@ -35,7 +35,7 @@ use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn mcp_server_status_list_returns_raw_server_and_tool_names() -> Result<()> {

View File

@@ -39,7 +39,7 @@ use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(30);
const TEST_SERVER_NAME: &str = "tool_server";
const TEST_TOOL_NAME: &str = "echo_tool";

View File

@@ -17,7 +17,7 @@ use tempfile::TempDir;
use tokio::time::timeout;
use uuid::Uuid;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn memory_reset_clears_memory_files_and_rows_preserves_threads() -> Result<()> {

View File

@@ -17,7 +17,7 @@ use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
const INVALID_REQUEST_ERROR_CODE: i64 = -32600;
fn model_from_preset(preset: &ModelPreset) -> Model {

View File

@@ -15,7 +15,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn turn_start_accepts_output_schema_v2() -> Result<()> {

View File

@@ -33,7 +33,7 @@ use tokio::time::sleep;
use tokio::time::timeout;
use wiremock::MockServer;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn plan_mode_uses_proposed_plan_block_for_plan_item() -> Result<()> {

View File

@@ -51,7 +51,7 @@ use wiremock::matchers::header;
use wiremock::matchers::method;
use wiremock::matchers::path;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn plugin_install_rejects_relative_marketplace_paths() -> Result<()> {

View File

@@ -30,7 +30,7 @@ use wiremock::matchers::method;
use wiremock::matchers::path;
use wiremock::matchers::query_param;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
const STARTUP_REMOTE_PLUGIN_SYNC_MARKER_FILE: &str = ".tmp/app-server-remote-plugin-sync-v1";
@@ -733,10 +733,18 @@ async fn plugin_list_accepts_legacy_string_default_prompt() -> Result<()> {
#[tokio::test]
async fn plugin_list_force_remote_sync_returns_remote_sync_error_on_fail_open() -> Result<()> {
let codex_home = TempDir::new()?;
write_plugin_sync_config(codex_home.path(), "https://chatgpt.com/backend-api/")?;
let server = MockServer::start().await;
write_plugin_sync_config(codex_home.path(), &format!("{}/backend-api/", server.uri()))?;
write_openai_curated_marketplace(codex_home.path(), &["linear"])?;
write_installed_plugin(&codex_home, "openai-curated", "linear")?;
Mock::given(method("GET"))
.and(path("/backend-api/plugins/featured"))
.and(query_param("platform", "codex"))
.respond_with(ResponseTemplate::new(200).set_body_string("[]"))
.mount(&server)
.await;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;

View File

@@ -43,7 +43,7 @@ use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn plugin_read_returns_plugin_details_with_bundle_contents() -> Result<()> {

View File

@@ -23,7 +23,7 @@ use wiremock::matchers::header;
use wiremock::matchers::method;
use wiremock::matchers::path;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[tokio::test]
async fn plugin_uninstall_removes_plugin_cache_and_config_entry() -> Result<()> {

View File

@@ -24,7 +24,7 @@ use wiremock::matchers::header;
use wiremock::matchers::method;
use wiremock::matchers::path;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const INVALID_REQUEST_ERROR_CODE: i64 = -32600;
#[tokio::test]

View File

@@ -71,7 +71,7 @@ use wiremock::matchers::method;
use wiremock::matchers::path;
use wiremock::matchers::path_regex;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
const STARTUP_CONTEXT_HEADER: &str = "Startup context from Codex.";
const V2_STEERING_ACKNOWLEDGEMENT: &str =
"This was sent to steer the previous background agent task.";

View File

@@ -18,7 +18,7 @@ use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::UserInput as V2UserInput;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn request_permissions_round_trip() -> Result<()> {

View File

@@ -20,7 +20,7 @@ use codex_protocol::config_types::Settings;
use codex_protocol::openai_models::ReasoningEffort;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn request_user_input_round_trip() -> Result<()> {

View File

@@ -30,7 +30,7 @@ use serde_json::json;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const INVALID_REQUEST_ERROR_CODE: i64 = -32600;
#[tokio::test]

View File

@@ -20,7 +20,7 @@ use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const REQUESTED_MODEL: &str = "gpt-5.1-codex-max";
const SERVER_MODEL: &str = "gpt-5.2-codex";

View File

@@ -16,7 +16,7 @@ use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
const WATCHER_TIMEOUT: Duration = Duration::from_secs(20);
fn write_skill(root: &TempDir, name: &str) -> Result<()> {

View File

@@ -25,7 +25,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_archive_requires_materialized_rollout() -> Result<()> {

View File

@@ -43,7 +43,7 @@ use super::analytics::enable_analytics_capture;
use super::analytics::thread_initialized_event;
use super::analytics::wait_for_analytics_payload;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_fork_creates_new_thread_and_emits_started() -> Result<()> {

View File

@@ -21,7 +21,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_inject_items_adds_raw_response_items_to_thread_history() -> Result<()> {

View File

@@ -42,7 +42,7 @@ use tempfile::TempDir;
use tokio::time::timeout;
use uuid::Uuid;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
async fn init_mcp(codex_home: &Path) -> Result<McpProcess> {
let mut mcp = McpProcess::new(codex_home).await?;

View File

@@ -13,7 +13,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_loaded_list_returns_loaded_thread_ids() -> Result<()> {

View File

@@ -18,7 +18,7 @@ use std::sync::Arc;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_memory_mode_set_updates_loaded_thread_state() -> Result<()> {

View File

@@ -32,7 +32,7 @@ use std::sync::Arc;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_metadata_update_patches_git_branch_and_returns_updated_thread() -> Result<()> {

View File

@@ -36,7 +36,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_read_returns_summary_without_turns() -> Result<()> {

View File

@@ -76,7 +76,7 @@ use super::analytics::enable_analytics_capture;
use super::analytics::thread_initialized_event;
use super::analytics::wait_for_analytics_payload;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const CODEX_5_2_INSTRUCTIONS_TEMPLATE_DEFAULT: &str = "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.";
async fn wait_for_responses_request_count(

View File

@@ -20,7 +20,7 @@ use serde_json::Value;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_rollback_drops_last_turns_and_persists_to_rollout() -> Result<()> {

View File

@@ -35,7 +35,7 @@ use std::path::Path;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_shell_command_runs_as_standalone_turn_and_persists_history() -> Result<()> {

View File

@@ -44,7 +44,7 @@ use super::analytics::mount_analytics_capture;
use super::analytics::thread_initialized_event;
use super::analytics::wait_for_analytics_payload;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn thread_start_creates_thread_and_emits_started() -> Result<()> {

View File

@@ -19,7 +19,7 @@ use codex_app_server_protocol::UserInput as V2UserInput;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn thread_status_changed_emits_runtime_updates() -> Result<()> {

View File

@@ -4,7 +4,7 @@ use app_test_support::McpProcess;
use app_test_support::create_final_assistant_message_sse_response;
use app_test_support::create_mock_responses_server_repeating_assistant;
use app_test_support::create_mock_responses_server_sequence_unchecked;
use app_test_support::create_shell_command_sse_response;
use app_test_support::create_shell_command_sse_response_from_command;
use app_test_support::to_response;
use codex_app_server_protocol::ItemStartedNotification;
use codex_app_server_protocol::JSONRPCResponse;
@@ -30,7 +30,7 @@ use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
async fn wait_for_responses_request_count_to_stabilize(
server: &wiremock::MockServer,
@@ -129,13 +129,9 @@ async fn thread_unsubscribe_keeps_thread_loaded_until_idle_timeout() -> Result<(
#[tokio::test]
async fn thread_unsubscribe_during_turn_keeps_turn_running() -> Result<()> {
#[cfg(target_os = "windows")]
let shell_command = vec![
"powershell".to_string(),
"-Command".to_string(),
"Start-Sleep -Seconds 1".to_string(),
];
let shell_command = "Start-Sleep -Seconds 1";
#[cfg(not(target_os = "windows"))]
let shell_command = vec!["sleep".to_string(), "1".to_string()];
let shell_command = "sleep 1";
let tmp = TempDir::new()?;
let codex_home = tmp.path().join("codex_home");
@@ -144,11 +140,12 @@ async fn thread_unsubscribe_during_turn_keeps_turn_running() -> Result<()> {
std::fs::create_dir(&working_directory)?;
let server = create_mock_responses_server_sequence_unchecked(vec![
create_shell_command_sse_response(
shell_command.clone(),
create_shell_command_sse_response_from_command(
shell_command,
Some(&working_directory),
Some(10_000),
"call_sleep",
Some(false),
)?,
create_final_assistant_message_sse_response("Done")?,
])

View File

@@ -23,7 +23,7 @@ use codex_app_server_protocol::UserInput as V2UserInput;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn turn_interrupt_aborts_running_turn() -> Result<()> {

View File

@@ -73,7 +73,7 @@ use super::analytics::wait_for_analytics_event;
#[cfg(windows)]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
#[cfg(not(windows))]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
const TEST_ORIGINATOR: &str = "codex_vscode";
const LOCAL_PRAGMATIC_TEMPLATE: &str = "You are a deeply pragmatic, effective software engineer.";

View File

@@ -43,7 +43,7 @@ use tokio::time::timeout;
#[cfg(windows)]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15);
#[cfg(not(windows))]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> {

View File

@@ -27,7 +27,7 @@ use tokio::time::timeout;
use super::analytics::enable_analytics_capture;
use super::analytics::wait_for_analytics_event;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn turn_steer_requires_active_turn() -> Result<()> {

View File

@@ -15,7 +15,7 @@ use std::collections::BTreeMap;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
#[tokio::test]
async fn windows_sandbox_setup_start_emits_completion_notification() -> Result<()> {

View File

@@ -186,7 +186,7 @@ async fn wait_for_subagent_notification(parent_thread: &Arc<CodexThread>) -> boo
sleep(Duration::from_millis(25)).await;
}
};
timeout(Duration::from_secs(2), wait).await.is_ok()
timeout(Duration::from_secs(30), wait).await.is_ok()
}
async fn persist_thread_for_tree_resume(thread: &Arc<CodexThread>, message: &str) {

View File

@@ -252,6 +252,7 @@ async fn exec_full_buffer_capture_ignores_expiration() -> Result<()> {
"powershell.exe".to_string(),
"-NonInteractive".to_string(),
"-NoLogo".to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
"Start-Sleep -Milliseconds 50; [Console]::Out.Write('hello')".to_string(),
];
@@ -334,6 +335,7 @@ async fn process_exec_tool_call_preserves_full_buffer_capture_policy() -> Result
"powershell.exe".to_string(),
"-NonInteractive".to_string(),
"-NoLogo".to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
format!("Start-Sleep -Milliseconds 50; [Console]::Out.Write('a' * {byte_count})"),
];
@@ -1014,6 +1016,7 @@ fn long_running_command() -> Vec<String> {
"powershell.exe".to_string(),
"-NonInteractive".to_string(),
"-NoLogo".to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
"Start-Sleep -Seconds 30".to_string(),
]

View File

@@ -276,12 +276,18 @@ mod tests {
let config = fs::read_to_string(codex_home.path().join(codex_config::CONFIG_TOML_FILE))?;
let config: toml::Value = toml::from_str(&config)?;
let marketplace = config
.get("marketplaces")
.and_then(toml::Value::as_table)
.and_then(|marketplaces| marketplaces.get("debug"))
.and_then(toml::Value::as_table)
.expect("debug marketplace should be present in config");
assert_eq!(
config["marketplaces"]["debug"]["source_type"].as_str(),
marketplace.get("source_type").and_then(toml::Value::as_str),
Some("local")
);
assert_eq!(
config["marketplaces"]["debug"]["source"].as_str(),
marketplace.get("source").and_then(toml::Value::as_str),
Some(expected_source.as_str())
);
Ok(())

View File

@@ -130,6 +130,16 @@ fn looks_like_local_path(source: &str) -> bool {
|| source.starts_with("~/")
|| source == "."
|| source == ".."
|| {
#[cfg(windows)]
{
source.starts_with(".\\") || source.starts_with("..\\") || source.starts_with('\\')
}
#[cfg(not(windows))]
{
false
}
}
}
fn resolve_local_source_path(source: &str) -> Result<PathBuf, MarketplaceAddError> {
@@ -310,6 +320,14 @@ mod tests {
assert!(path.is_absolute());
}
#[cfg(windows)]
#[test]
fn windows_backslash_relative_path_looks_local() {
assert!(looks_like_local_path(r".\marketplace"));
assert!(looks_like_local_path(r"..\marketplace"));
assert!(looks_like_local_path(r"\marketplace"));
}
#[test]
fn local_file_source_is_rejected() {
let tempdir = TempDir::new().unwrap();

View File

@@ -55,6 +55,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
use tokio::sync::Notify;
use tokio::time::timeout;
use tokio_util::sync::CancellationToken;
@@ -153,33 +154,35 @@ async fn wait_for_turn_aborted(
expected_turn_id: &str,
expected_reason: TurnAbortReason,
) {
timeout(Duration::from_secs(5), async {
loop {
let event = thread
.next_event()
.await
.expect("child thread should emit events");
if matches!(
event.msg,
EventMsg::TurnAborted(TurnAbortedEvent {
turn_id: Some(ref turn_id),
ref reason,
..
}) if turn_id == expected_turn_id && *reason == expected_reason
) {
let deadline = tokio::time::Instant::now() + Duration::from_secs(15);
let mut seen_aborts = Vec::new();
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
let event = timeout(remaining, thread.next_event())
.await
.unwrap_or_else(|_| {
panic!(
"expected child turn {expected_turn_id} to be aborted with {expected_reason:?}; saw aborts: {seen_aborts:?}"
)
})
.expect("child thread should emit events");
if let EventMsg::TurnAborted(TurnAbortedEvent {
turn_id, reason, ..
}) = event.msg
{
if turn_id.as_deref() == Some(expected_turn_id) && reason == expected_reason {
break;
}
seen_aborts.push((turn_id, reason));
}
})
.await
.expect("expected child turn to be interrupted");
}
}
async fn wait_for_redirected_envelope_in_history(
thread: &Arc<CodexThread>,
expected: &InterAgentCommunication,
) {
timeout(Duration::from_secs(5), async {
timeout(Duration::from_secs(30), async {
loop {
let history_items = thread
.codex
@@ -216,8 +219,9 @@ async fn wait_for_redirected_envelope_in_history(
.expect("redirected followup envelope should appear in history");
}
#[derive(Clone, Copy)]
struct NeverEndingTask;
struct NeverEndingTask {
started: Arc<Notify>,
}
impl SessionTask for NeverEndingTask {
fn kind(&self) -> TaskKind {
@@ -235,6 +239,7 @@ impl SessionTask for NeverEndingTask {
_input: Vec<UserInput>,
cancellation_token: CancellationToken,
) -> Option<String> {
self.started.notify_one();
cancellation_token.cancelled().await;
None
}
@@ -1518,6 +1523,7 @@ async fn multi_agent_v2_followup_task_interrupts_busy_child_without_losing_messa
let active_turn = thread.codex.session.new_default_turn().await;
let interrupted_turn_id = active_turn.sub_id.clone();
let never_ending_task_started = Arc::new(Notify::new());
thread
.codex
.session
@@ -1527,9 +1533,17 @@ async fn multi_agent_v2_followup_task_interrupts_busy_child_without_losing_messa
text: "working".to_string(),
text_elements: Vec::new(),
}],
NeverEndingTask,
NeverEndingTask {
started: Arc::clone(&never_ending_task_started),
},
)
.await;
timeout(
Duration::from_secs(15),
never_ending_task_started.notified(),
)
.await
.expect("child task should start before the interrupting followup");
FollowupTaskHandlerV2
.handle(invocation(

View File

@@ -14,6 +14,7 @@ pub fn create_shell_command_sse_response(
"command": command_str,
"workdir": workdir.map(|w| w.to_string_lossy()),
"timeout_ms": timeout_ms,
"login": false,
}))?;
let response_id = format!("resp-{call_id}");
Ok(responses::sse(vec![

View File

@@ -27,7 +27,7 @@ use mcp_test_support::create_apply_patch_sse_response;
use mcp_test_support::create_final_assistant_message_sse_response;
use mcp_test_support::create_mock_responses_server;
use mcp_test_support::create_shell_command_sse_response;
use mcp_test_support::format_with_current_shell;
use mcp_test_support::format_with_current_shell_non_login;
// Allow ample time on slower CI or under load to avoid flakes.
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(20);
@@ -70,8 +70,8 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> {
created_filename.to_string(),
"-Force".to_string(),
],
// `powershell.exe` startup can be slow on loaded Windows CI workers
10_000,
// `powershell.exe` startup can be slow on loaded Windows CI workers.
30_000,
)
} else {
(
@@ -79,8 +79,9 @@ async fn shell_command_approval_triggers_elicitation() -> anyhow::Result<()> {
5_000,
)
};
let expected_shell_command =
format_with_current_shell(&shlex::try_join(shell_command.iter().map(String::as_str))?);
let expected_shell_command = format_with_current_shell_non_login(&shlex::try_join(
shell_command.iter().map(String::as_str),
)?);
let McpHandle {
process: mut mcp_process,

View File

@@ -8153,6 +8153,17 @@ mod tests {
let (mut app, _app_event_rx, _op_rx) = make_test_app_with_channels().await;
let codex_home = tempdir()?;
app.config.codex_home = codex_home.path().to_path_buf().abs();
app.config.sqlite_home = codex_home.path().to_path_buf();
let state_db = codex_state::StateRuntime::init(
codex_home.path().to_path_buf(),
app.config.model_provider_id.clone(),
)
.await
.expect("state db should initialize");
state_db
.mark_backfill_complete(/*last_watermark*/ None)
.await
.expect("state db backfill should be marked complete");
let mut app_server = crate::start_embedded_app_server_for_picker(&app.config).await?;
let started = app_server.start_thread(&app.config).await?;
@@ -8166,16 +8177,20 @@ mod tests {
)
.await;
let state_db = codex_state::StateRuntime::init(
codex_home.path().to_path_buf(),
app.config.model_provider_id.clone(),
)
let memory_mode = time::timeout(Duration::from_secs(10), async {
loop {
let memory_mode = state_db
.get_thread_memory_mode(thread_id)
.await
.expect("thread memory mode should be readable");
if memory_mode.as_deref() == Some("disabled") {
break memory_mode;
}
time::sleep(Duration::from_millis(50)).await;
}
})
.await
.expect("state db should initialize");
let memory_mode = state_db
.get_thread_memory_mode(thread_id)
.await
.expect("thread memory mode should be readable");
.expect("thread memory mode should update");
assert_eq!(memory_mode.as_deref(), Some("disabled"));
app_server.shutdown().await?;