1use reqwest::StatusCode;
2use serde_json;
3use std::io;
4use std::time::Duration;
5use thiserror::Error;
6use tokio::task::JoinError;
7use uuid::Uuid;
8
9pub type Result<T> = std::result::Result<T, CodexErr>;
10
11#[derive(Error, Debug)]
12pub enum SandboxErr {
13 #[error("sandbox denied exec error, exit code: {0}, stdout: {1}, stderr: {2}")]
15 Denied(i32, String, String),
16
17 #[cfg(target_os = "linux")]
19 #[error("seccomp setup error")]
20 SeccompInstall(#[from] seccompiler::Error),
21
22 #[cfg(target_os = "linux")]
24 #[error("seccomp backend error")]
25 SeccompBackend(#[from] seccompiler::BackendError),
26
27 #[error("command timed out")]
29 Timeout,
30
31 #[error("command was killed by a signal")]
33 Signal(i32),
34
35 #[error("Landlock was not able to fully enforce all sandbox rules")]
37 LandlockRestrict,
38}
39
40#[derive(Error, Debug)]
41pub enum CodexErr {
42 #[error("stream disconnected before completion: {0}")]
49 Stream(String, Option<Duration>),
50
51 #[error("no conversation with id: {0}")]
52 ConversationNotFound(Uuid),
53
54 #[error("session configured event was not the first event in the stream")]
55 SessionConfiguredNotFirstEvent,
56
57 #[error("timeout waiting for child process to exit")]
59 Timeout,
60
61 #[error("spawn failed: child stdout/stderr not captured")]
64 Spawn,
65
66 #[error("interrupted (Ctrl-C)")]
69 Interrupted,
70
71 #[error("unexpected status {0}: {1}")]
73 UnexpectedStatus(StatusCode, String),
74
75 #[error("{0}")]
76 UsageLimitReached(UsageLimitReachedError),
77
78 #[error(
79 "To use Codex with your ChatGPT plan, upgrade to Plus: https://openai.com/chatgpt/pricing."
80 )]
81 UsageNotIncluded,
82
83 #[error("We're currently experiencing high demand, which may cause temporary errors.")]
84 InternalServerError,
85
86 #[error("exceeded retry limit, last status: {0}")]
88 RetryLimit(StatusCode),
89
90 #[error("internal error; agent loop died unexpectedly")]
92 InternalAgentDied,
93
94 #[error("sandbox error: {0}")]
96 Sandbox(#[from] SandboxErr),
97
98 #[error("agcodex-linux-sandbox was required but not provided")]
99 LandlockSandboxExecutableNotProvided,
100
101 #[error(transparent)]
105 Io(#[from] io::Error),
106
107 #[error(transparent)]
108 Reqwest(#[from] reqwest::Error),
109
110 #[error(transparent)]
111 Json(#[from] serde_json::Error),
112
113 #[cfg(target_os = "linux")]
114 #[error(transparent)]
115 LandlockRuleset(#[from] landlock::RulesetError),
116
117 #[cfg(target_os = "linux")]
118 #[error(transparent)]
119 LandlockPathFd(#[from] landlock::PathFdError),
120
121 #[error(transparent)]
122 TokioJoin(#[from] JoinError),
123
124 #[error("{0}")]
125 EnvVar(EnvVarError),
126
127 #[error("MCP server error: {0}")]
129 McpServer(String),
130
131 #[error("MCP client start failed for server {server}: {error}")]
132 McpClientStart { server: String, error: String },
133
134 #[error("MCP tool not found: {0}")]
135 McpToolNotFound(String),
136
137 #[error("invalid configuration: {0}")]
139 InvalidConfig(String),
140
141 #[error("invalid working directory: {0}")]
142 InvalidWorkingDirectory(String),
143
144 #[error("operation not allowed in current mode: {0}")]
146 ModeRestriction(String),
147
148 #[error("no branch point available for creating branch")]
150 NoBranchPointAvailable,
151
152 #[error("no current state available for creating checkpoint")]
153 NoCurrentStateForCheckpoint,
154
155 #[error("snapshot {0} not found")]
156 SnapshotNotFound(Uuid),
157
158 #[error("branch {0} not found")]
159 BranchNotFound(Uuid),
160
161 #[error("undo stack is empty")]
162 UndoStackEmpty,
163
164 #[error("redo stack is empty")]
165 RedoStackEmpty,
166
167 #[error("memory limit exceeded: {current} > {limit} bytes")]
168 MemoryLimitExceeded { current: usize, limit: usize },
169
170 #[error("{0}")]
172 General(String),
173}
174
175#[derive(Debug)]
176pub struct UsageLimitReachedError {
177 pub plan_type: Option<String>,
178}
179
180impl std::fmt::Display for UsageLimitReachedError {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 if let Some(plan_type) = &self.plan_type
183 && plan_type == "plus"
184 {
185 write!(
186 f,
187 "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), or wait for limits to reset (every 5h and every week.)."
188 )?;
189 } else {
190 write!(
191 f,
192 "You've hit your usage limit. Limits reset every 5h and every week."
193 )?;
194 }
195 Ok(())
196 }
197}
198
199#[derive(Debug)]
200pub struct EnvVarError {
201 pub var: String,
203
204 pub instructions: Option<String>,
207}
208
209impl std::fmt::Display for EnvVarError {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 write!(f, "Missing environment variable: `{}`.", self.var)?;
212 if let Some(instructions) = &self.instructions {
213 write!(f, " {instructions}")?;
214 }
215 Ok(())
216 }
217}
218
219impl CodexErr {
220 pub fn downcast_ref<T: std::any::Any>(&self) -> Option<&T> {
224 (self as &dyn std::any::Any).downcast_ref::<T>()
225 }
226}
227
228pub fn get_error_message_ui(e: &CodexErr) -> String {
229 match e {
230 CodexErr::Sandbox(SandboxErr::Denied(_, _, stderr)) => stderr.to_string(),
231 _ => e.to_string(),
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn usage_limit_reached_error_formats_plus_plan() {
241 let err = UsageLimitReachedError {
242 plan_type: Some("plus".to_string()),
243 };
244 assert_eq!(
245 err.to_string(),
246 "You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), or wait for limits to reset (every 5h and every week.)."
247 );
248 }
249
250 #[test]
251 fn usage_limit_reached_error_formats_default_when_none() {
252 let err = UsageLimitReachedError { plan_type: None };
253 assert_eq!(
254 err.to_string(),
255 "You've hit your usage limit. Limits reset every 5h and every week."
256 );
257 }
258
259 #[test]
260 fn usage_limit_reached_error_formats_default_for_other_plans() {
261 let err = UsageLimitReachedError {
262 plan_type: Some("pro".to_string()),
263 };
264 assert_eq!(
265 err.to_string(),
266 "You've hit your usage limit. Limits reset every 5h and every week."
267 );
268 }
269}