mirror of
https://github.com/openai/codex.git
synced 2026-02-19 15:23:46 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1f91433bc |
@@ -73,6 +73,44 @@ pub(crate) async fn spawn_child_async(
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// macOS network stack (via reqwest/hyper) opens PF_SYSTEM control sockets
|
||||
// such as com.apple.netsrc without setting FD_CLOEXEC. Those descriptors
|
||||
// get inherited by shell tool children, which is surprising and lets the
|
||||
// child talk to that kernel control socket. Close everything above stdio
|
||||
// to keep those sockets (and any similar long-lived fds) out of subshells.
|
||||
// We bound the sweep by min(RLIMIT_NOFILE, 128) because netsrc fds are
|
||||
// low and this avoids a pathological case where another thread lowers
|
||||
// the rlimit between opening the socket and spawning the child. If we ever
|
||||
// move the reqwest traffic into a helper process, leaking these fds would
|
||||
// be less concerning.
|
||||
let mut max_fd = 128_i64;
|
||||
let mut limit = libc::rlimit {
|
||||
rlim_cur: 0,
|
||||
rlim_max: 0,
|
||||
};
|
||||
if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limit) == 0
|
||||
&& limit.rlim_cur != libc::RLIM_INFINITY
|
||||
{
|
||||
let soft_limit = limit.rlim_cur.min(i64::MAX as libc::rlim_t) as i64;
|
||||
max_fd = max_fd.min(soft_limit);
|
||||
}
|
||||
|
||||
let bound = max_fd.max(3);
|
||||
let mut fd = 3;
|
||||
while (fd as i64) < bound {
|
||||
let flags = libc::fcntl(fd, libc::F_GETFD);
|
||||
// We leave CLOEXEC fds alone (for example, the stdlib
|
||||
// error-reporting pipe used when exec fails) and only close
|
||||
// descriptors that would have been inherited.
|
||||
if flags != -1 && (flags & libc::FD_CLOEXEC) == 0 {
|
||||
libc::close(fd);
|
||||
}
|
||||
fd += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// This relies on prctl(2), so it only works on Linux.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
|
||||
@@ -446,6 +446,12 @@ pub async fn mount_sse_once(server: &MockServer, body: String) -> ResponseMock {
|
||||
response_mock
|
||||
}
|
||||
|
||||
pub async fn mount_sse(server: &MockServer, body: String) -> ResponseMock {
|
||||
let (mock, response_mock) = base_mock();
|
||||
mock.respond_with(sse_response(body)).mount(server).await;
|
||||
response_mock
|
||||
}
|
||||
|
||||
pub async fn start_mock_server() -> MockServer {
|
||||
MockServer::builder()
|
||||
.body_print_limit(BodyPrintLimit::Limited(80_000))
|
||||
|
||||
@@ -9,6 +9,7 @@ use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_custom_tool_call;
|
||||
use core_test_support::responses::ev_function_call;
|
||||
use core_test_support::responses::mount_sse;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
use core_test_support::responses::sse;
|
||||
use core_test_support::responses::start_mock_server;
|
||||
@@ -102,6 +103,8 @@ async fn process_sse_emits_failed_event_on_parse_error() {
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -141,6 +144,8 @@ async fn process_sse_records_failed_event_when_stream_closes_without_completed()
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -188,18 +193,12 @@ async fn process_sse_failed_event_records_response_error_message() {
|
||||
})]),
|
||||
)
|
||||
.await;
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -245,18 +244,12 @@ async fn process_sse_failed_event_logs_parse_error() {
|
||||
})]),
|
||||
)
|
||||
.await;
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -301,6 +294,8 @@ async fn process_sse_failed_event_logs_missing_error() {
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -342,18 +337,11 @@ async fn process_sse_failed_event_logs_response_completed_parse_error() {
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -442,7 +430,7 @@ async fn process_sse_emits_completed_telemetry() {
|
||||
async fn handle_response_item_records_tool_result_for_custom_tool_call() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_custom_tool_call(
|
||||
@@ -454,18 +442,12 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() {
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -512,7 +494,7 @@ async fn handle_response_item_records_tool_result_for_custom_tool_call() {
|
||||
async fn handle_response_item_records_tool_result_for_function_call() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_function_call("function-call", "nonexistent", "{\"value\":1}"),
|
||||
@@ -521,18 +503,11 @@ async fn handle_response_item_records_tool_result_for_function_call() {
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -579,7 +554,7 @@ async fn handle_response_item_records_tool_result_for_function_call() {
|
||||
async fn handle_response_item_records_tool_result_for_local_shell_missing_ids() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
serde_json::json!({
|
||||
@@ -598,18 +573,11 @@ async fn handle_response_item_records_tool_result_for_local_shell_missing_ids()
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -650,7 +618,7 @@ async fn handle_response_item_records_tool_result_for_local_shell_missing_ids()
|
||||
async fn handle_response_item_records_tool_result_for_local_shell_call() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call("shell-call", "completed", vec!["/bin/echo", "shell"]),
|
||||
@@ -659,18 +627,11 @@ async fn handle_response_item_records_tool_result_for_local_shell_call() {
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(move |config| {
|
||||
config.features.disable(Feature::GhostCommit);
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -749,23 +710,10 @@ fn tool_decision_assertion<'a>(
|
||||
#[traced_test]
|
||||
async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
||||
let server = start_mock_server().await;
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call(
|
||||
"auto_config_call",
|
||||
"completed",
|
||||
vec!["/bin/echo", "local shell"],
|
||||
),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_local_shell_call("auto_config_call", "completed", vec!["/bin/echo", "hello"]),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
@@ -775,6 +723,8 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::OnRequest;
|
||||
config.sandbox_policy = SandboxPolicy::DangerFullAccess;
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -789,7 +739,7 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await;
|
||||
wait_for_event(&codex, |ev| matches!(ev, EventMsg::TokenCount(_))).await;
|
||||
|
||||
logs_assert(tool_decision_assertion(
|
||||
"auto_config_call",
|
||||
@@ -802,7 +752,7 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
|
||||
#[traced_test]
|
||||
async fn handle_container_exec_user_approved_records_tool_decision() {
|
||||
let server = start_mock_server().await;
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call("user_approved_call", "completed", vec!["/bin/date"]),
|
||||
@@ -811,18 +761,11 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -861,7 +804,7 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
|
||||
async fn handle_container_exec_user_approved_for_session_records_tool_decision() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call("user_approved_session_call", "completed", vec!["/bin/date"]),
|
||||
@@ -869,18 +812,12 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -919,7 +856,7 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
|
||||
async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call("sandbox_retry_call", "completed", vec!["/bin/date"]),
|
||||
@@ -927,18 +864,12 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -977,7 +908,7 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
|
||||
async fn handle_container_exec_user_denies_records_tool_decision() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call("user_denied_call", "completed", vec!["/bin/date"]),
|
||||
@@ -986,17 +917,11 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -1035,7 +960,7 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
|
||||
async fn handle_sandbox_error_user_approves_for_session_records_tool_decision() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call("sandbox_session_call", "completed", vec!["/bin/date"]),
|
||||
@@ -1043,18 +968,12 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
@@ -1093,7 +1012,7 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
|
||||
async fn handle_sandbox_error_user_denies_records_tool_decision() {
|
||||
let server = start_mock_server().await;
|
||||
|
||||
mount_sse_once(
|
||||
mount_sse(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_local_shell_call("sandbox_deny_call", "completed", vec!["/bin/date"]),
|
||||
@@ -1102,18 +1021,11 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
|
||||
)
|
||||
.await;
|
||||
|
||||
mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_assistant_message("msg-1", "local shell done"),
|
||||
ev_completed("done"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_config(|config| {
|
||||
config.approval_policy = AskForApproval::UnlessTrusted;
|
||||
config.model_provider.request_max_retries = Some(0);
|
||||
config.model_provider.stream_max_retries = Some(0);
|
||||
})
|
||||
.build(&server)
|
||||
.await
|
||||
|
||||
@@ -42,55 +42,16 @@ Custom prompts turn your repeatable instructions into reusable slash commands, s
|
||||
|
||||
### Examples
|
||||
|
||||
### Example 1: Basic named arguments
|
||||
**Draft PR helper**
|
||||
|
||||
**File**: `~/.codex/prompts/ticket.md`
|
||||
`~/.codex/prompts/draftpr.md`
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: Generate a commit message for a ticket
|
||||
argument-hint: TICKET_ID=<id> TICKET_TITLE=<title>
|
||||
description: Create feature branch, commit and open draft PR.
|
||||
---
|
||||
|
||||
Please write a concise commit message for ticket $TICKET_ID: $TICKET_TITLE
|
||||
Create a branch named `tibo/<feature_name>`, commit the changes, and open a draft PR.
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
|
||||
```
|
||||
/prompts:ticket TICKET_ID=JIRA-1234 TICKET_TITLE="Fix login bug"
|
||||
```
|
||||
|
||||
**Expanded prompt sent to Codex**:
|
||||
|
||||
```
|
||||
Please write a concise commit message for ticket JIRA-1234: Fix login bug
|
||||
```
|
||||
|
||||
**Note**: Both `TICKET_ID` and `TICKET_TITLE` are required. If either is missing, Codex will show a validation error. Values with spaces must be double-quoted.
|
||||
|
||||
### Example 2: Mixed positional and named arguments
|
||||
|
||||
**File**: `~/.codex/prompts/review.md`
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: Review code in a specific file with focus area
|
||||
argument-hint: FILE=<path> [FOCUS=<section>]
|
||||
---
|
||||
|
||||
Review the code in $FILE. Pay special attention to $FOCUS.
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
|
||||
```
|
||||
/prompts:review FILE=src/auth.js FOCUS="error handling"
|
||||
```
|
||||
|
||||
**Expanded prompt**:
|
||||
|
||||
```
|
||||
Review the code in src/auth.js. Pay special attention to error handling.
|
||||
|
||||
```
|
||||
Usage: type `/prompts:draftpr` to have codex perform the work.
|
||||
|
||||
Reference in New Issue
Block a user