Cloud Tasks
codex cloud is the subcommand that drops a prompt onto the OpenAI cloud to run: locally it does not call the model or run the Agent loop; it only submits a task, pulls status, fetches the diff, and applies the patch. cloud-tasks is the TUI + CLI entry; cloud-tasks-client provides the CloudBackend trait + HTTP impl; cloud-tasks-mock-client plugs a fake backend for debug mode; backend-client is the underlying HTTP (also reused by other backend APIs). Unlike local inference in Model Provider — here "model inference" happens inside a cloud container.
Responsibilities
- Submit tasks:
run_exec_commandpackages prompt + git ref + environment id andPOST /wham/tasks(or/api/codex/tasks), returning a task id (codex-rs/cloud-tasks/src/lib.rs:161-184). - List / detail / diff:
run_list_command,run_status_command,run_diff_commandare three CLI subcommands mapping tolist_tasks/get_task_summary/get_task_diff(codex-rs/cloud-tasks/src/cli.rs:16-27). - Apply patches:
apply_taskruns a dry-run preflight before the real apply;diff_overridelets the user pick one attempt from best-of-N (codex-rs/cloud-tasks-client/src/http.rs:99-121). - Abstract trait:
CloudBackendcollapses all backend-interaction methods into a trait; debug builds useMockClient, release usesHttpClient(codex-rs/cloud-tasks-client/src/api.rs:136-176). - Initialize the backend:
init_backendassembles base URL, UA, auth provider, ChatGPT-Account-Id, and switches between WHAM / Codex API path style based onCODEX_CLOUD_TASKS_BASE_URL(codex-rs/cloud-tasks/src/lib.rs:43-107).
Design motivation
It's its own crate rather than stuffed into core, because this path does not run the Agent main loop. The client in core sends stream requests to the model, parses SSE, maintains turn state; a cloud task is "submit a long-running task to the cloud, wait for results asynchronously". The two concurrency models are completely different: the former is a long-lived streaming connection, the latter is poll + fetch. The CloudBackend trait exists so the TUI can run in debug builds — when init_backend detects CODEX_CLOUD_TASKS_MODE=mock, it swaps in MockClient, so the UI state machine can be exercised locally without really hitting the ChatGPT backend. HttpClient internally reuses backend-client::Client rather than spinning up its own HTTP, because backend-api's auth headers, Cloudflare cookie store, and path style (wham vs codex-api) are consistent with other backend API calls.
best-of-N is the signature of this path: create_task accepts attempts: usize (1–4); the cloud runs multiple attempts; list_sibling_attempts fetches them all back; ApplyCommand lets the user specify --attempt N to pick one. The diff_override field flows through apply_task_preflight and apply_task, so the apply stage does not re-fetch the diff but uses the user-selected one.
Key files
codex-rs/cloud-tasks/src/lib.rs:735-744 — run_main, subcommand dispatch + TUI mode entry.codex-rs/cloud-tasks/src/lib.rs:43-107 — BackendContext and init_backend, deciding mock vs http by env var and assembling auth.codex-rs/cloud-tasks/src/cli.rs:29-50 — ExecCommand, defining the three core args --env, --attempts, --branch.codex-rs/cloud-tasks-client/src/api.rs:136-176 — the CloudBackend trait, 11 methods covering list / get / apply / create.codex-rs/cloud-tasks-client/src/http.rs:25-63 — HttpClient, wrapping backend-client::Client and implementing CloudBackend.codex-rs/backend-client/src/client.rs:451-482 — create_task impl in the base client, extracting the task id from JSON.codex-rs/cloud-tasks-mock-client/src/mock.rs:163-189 — MockClient implements the trait, delegating to internal mock data generators.The factory init_backend checks env vars in debug builds, deciding between mock and real HTTP:
// cloud-tasks/src/lib.rs:43-60 — 后端选择
async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext> {
#[cfg(debug_assertions)]
let use_mock = matches!(
std::env::var("CODEX_CLOUD_TASKS_MODE").ok().as_deref(),
Some("mock") | Some("MOCK")
);
let base_url = std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string());
#[cfg(debug_assertions)]
if use_mock {
return Ok(BackendContext {
backend: Arc::new(codex_cloud_tasks_mock_client::MockClient),
base_url,
});
}The CloudBackend trait uses CloudBackendFuture to unify return types as boxed futures; mock and http impls share the same signature:
// cloud-tasks-client/src/api.rs:136-176 — 后端抽象
pub trait CloudBackend: Send + Sync {
fn list_tasks<'a>(
&'a self,
env: Option<&'a str>,
limit: Option<i64>,
cursor: Option<&'a str>,
) -> CloudBackendFuture<'a, TaskListPage>;
fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary>;
fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>>;
// ...messages / sibling attempts / apply preflight / apply / create
fn create_task<'a>(
&'a self,
env_id: &'a str,
prompt: &'a str,
git_ref: &'a str,
qa_mode: bool,
best_of_n: usize,
) -> CloudBackendFuture<'a, CreatedTask>;
}create_task POSTs from the base client and then extracts the task id from JSON (preferring task.id, falling back to the top-level id):
// backend-client/src/client.rs:451-482 — POST /wham/tasks 并提取 task id
pub async fn create_task(&self, request_body: serde_json::Value) -> Result<String> {
let url = match self.path_style {
PathStyle::CodexApi => format!("{}/api/codex/tasks", self.base_url),
PathStyle::ChatGptApi => format!("{}/wham/tasks", self.base_url),
};
// ...发请求,解析 body
match serde_json::from_str::<serde_json::Value>(&body) {
Ok(v) => {
if let Some(id) = v.get("task").and_then(|t| t.get("id")).and_then(|s| s.as_str()) {
Ok(id.to_string())
} else if let Some(id) = v.get("id").and_then(|s| s.as_str()) {
Ok(id.to_string())
} else {
anyhow::bail!("POST {url} succeeded but no task id found; ...");
}
}
Err(e) => anyhow::bail!("Decode error for {url}: {e}; ..."),
}
}Data flow
Boundaries and failures
- Not logged in exits directly:
init_backendchecksauthis None oruses_codex_backend()is false andprocess::exit(1)s (codex-rs/cloud-tasks/src/lib.rs:76-95). - Path style is decided by base URL:
https://chatgpt.com/backend-apigoes WHAM (/wham/tasks), others go Codex API (/api/codex/tasks), decided byPathStyle::from_base_url(codex-rs/backend-client/src/client.rs:151-177). - Attempts range is constrained:
parse_attemptsforces 1–4, aligned with the backend best-of-N cap (codex-rs/cloud-tasks/src/cli.rs:52-61). - Apply failure exits non-zero:
run_apply_commandchecksApplyStatus; if notSuccess, itprocess::exit(1), friendly for script chains (codex-rs/cloud-tasks/src/lib.rs:589-608). create_taskid accepts two JSON forms: bothtask.idand top-levelidare accepted, because different backends return different structures (codex-rs/backend-client/src/client.rs:464-481).
Summary
Cloud Tasks compresses "submit, query, apply" into the CloudBackend trait; HTTP and mock share the same trait, making TUI debugging possible without a local backend. The underlying HTTP reuses backend-client::Client, sharing auth and path-style decisions with Model Provider. Cloud-task-specific config (allowed environments, enterprise policies) is provided by the cloud config bundle in config system, fetched and applied at startup.