projects/guix·blob ruby-team · refs

1116 lines · 70.3 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2026 Danny Milosavljevic <[email protected]>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19;;; This module is separate from (gnu packages rust-apps) to avoid a
20;;; circular module dependency: (gnu packages rust-sources), which
21;;; defines rust-codex packages used by codex-acp, transitively loads
22;;; (gnu packages rust-apps) through its #:use-module chain. If the
23;;; codex-acp package lived in rust-apps.scm, loading rust-sources would
24;;; trigger loading rust-apps before those rust-codex packages are
25;;; defined, causing an unbound variable error.
26
27(define-module (gnu packages codex)
28 #:use-module ((guix licenses) #:prefix license:)
29 #:use-module (guix gexp)
30 #:use-module (guix packages)
31 #:use-module (guix download)
32 #:use-module (guix git-download)
33 #:use-module (guix search-paths)
34 #:use-module (guix utils)
35 #:use-module (guix build-system cargo)
36 #:use-module (srfi srfi-1)
37 #:use-module (gnu packages)
38 #:use-module (gnu packages bash)
39 #:use-module (gnu packages base)
40 #:use-module (gnu packages cmake)
41 #:use-module (gnu packages compression)
42 #:use-module (gnu packages curl)
43 #:use-module (gnu packages gdb)
44 #:use-module (gnu packages linux)
45 #:use-module (gnu packages libunwind)
46 #:use-module (gnu packages llvm)
47 #:use-module (gnu packages llvm-meta)
48 #:use-module (gnu packages lsof)
49 #:use-module (gnu packages perl)
50 #:use-module (gnu packages pkg-config)
51 #:use-module (gnu packages python)
52 #:use-module (gnu packages regex)
53 #:use-module (gnu packages rust)
54 #:use-module (gnu packages rust-sources)
55 #:use-module (gnu packages sqlite)
56 #:use-module (gnu packages nss)
57 #:use-module (gnu packages tls)
58 #:use-module (gnu packages version-control)
59 #:use-module (gnu packages virtualization))
60
61(define-public codex
62 (package
63 (name "codex")
64 (version "0.144.1")
65 (source
66 (origin
67 (method git-fetch)
68 (uri (git-reference
69 (url "https://github.com/openai/codex")
70 (commit "44918ea10c0f99151c6710411b4322c2f5c96bea")))
71 (file-name (git-file-name name version))
72 (sha256
73 (base32 "0icwnvss0zswsp0vz2cffzr4xq46zfv634cl9phn40kjhsl2ny18"))
74 (modules '((guix build utils)))
75 (snippet '(begin
76 ;; Bundled bubblewrap source tree; includes a
77 ;; compiled BPF blob (demos/flatpak.bpf).
78 (delete-file-recursively "codex-rs/vendor/bubblewrap")))
79 (patches (search-patches
80 "codex-0.144.1-disable-code-mode.patch"
81 "codex-0.144.1-remove-patch-sections.patch"
82 "codex-0.144.1-remove-libwebrtc.patch"
83 "codex-0.144.1-test-disable-network-model-refresh.patch"
84 "codex-0.144.1-test-selected-capability-readiness.patch"
85 "codex-0.144.1-test-shebangs.patch"
86 "codex-0.144.1-test-timeout.patch"))))
87 (build-system cargo-build-system)
88 (arguments
89 (list
90 #:rust rust-1.94
91 #:install-source? #f
92 ;; exec-server is library-only in this release (no [[bin]] in
93 ;; exec-server/Cargo.toml); cargo install --path exec-server fails
94 ;; with "no packages found with binaries or examples". Drop it
95 ;; from the install paths -- the library is consumed via the
96 ;; workspace by other binaries here, not installed standalone.
97 #:cargo-install-paths '(list "cli" "exec"
98 "linux-sandbox" "mcp-server"
99 "app-server" "tui")
100 ;; schema_fixtures_match_generated (upstream fixture is stale:
101 ;; FileChange::Update in codex-protocol gained old_content,
102 ;; new_content, move_path fields but the committed JSON schema
103 ;; fixture was not regenerated).
104 #:cargo-test-flags '(list "--workspace"
105 "--exclude" "codex-app-server-protocol"
106 "--exclude" "codex-code-mode"
107 "--exclude" "codex-code-mode-host"
108 ;;; BEGIN rustdoc absent from rust-1.94
109 ;; rust-1.94 installs rustc and cargo but
110 ;; not rustdoc. Select the ordinary test
111 ;; targets explicitly, excluding doctests.
112 "--lib" "--bins" "--tests"
113 ;;; END rustdoc absent from rust-1.94
114 "--"
115 ;;; BEGIN Landlock returns NotEnforced
116 ;;; in the build container; the sandbox
117 ;;; binary panics instead of cleanly
118 ;;; denying filesystem access.
119 ;; Sandbox denial and escalation tests.
120 "--skip" "sandbox_denied_shell_returns_original_output"
121 ;; Sandbox denial and escalation tests.
122 "--skip" "shell_escalated_permissions_rejected_then_ok"
123 ;; Sandbox denial and escalation tests.
124 "--skip" "unified_exec_runs_under_sandbox"
125 ;; codex-exec: spawn_command_under_linux_sandbox
126 ;; panics with LandlockRestrict (exit 101).
127 "--skip" "python_getpwuid_works_under_sandbox"
128 ;; Same as above.
129 "--skip" "python_multiprocessing_lock_works_under_sandbox"
130 ;; Same as above.
131 "--skip" "sandbox_distinguishes_command_and_policy_cwds"
132 ;; linux-sandbox: process_exec_tool_call
133 ;; invokes Landlock; same panic.
134 "--skip" "test_writable_root"
135 ;; Same as above.
136 "--skip" "test_timeout"
137 ;; Same as above.
138 "--skip" "test_root_read"
139 ;; Same as above.
140 "--skip" "test_dev_null_write"
141 ;; Same as above.
142 "--skip" "test_no_new_privs_is_enabled"
143 ;; One scenario runs through Landlock
144 ;; which panics; cargo --skip cannot
145 ;; target individual scenarios.
146 "--skip" "approval_matrix_covers_all_modes"
147 ;; Landlock panic triggers escalation-retry
148 ;; which interferes with the approval cache.
149 "--skip" "approving_apply_patch_for_session_skips_future_prompts_for_same_file"
150 ;; These tests expect to interrupt a
151 ;; long-running 'sleep 60' and receive
152 ;; TurnAborted. What happens:
153 ;;
154 ;; 1. ReadOnly wraps the command with
155 ;; codex-linux-sandbox (Landlock-based).
156 ;; 2. Landlock is unavailable, so the
157 ;; sandbox binary exits instantly.
158 ;; 3. Orchestrator gets SandboxErr::Denied.
159 ;; wants_no_sandbox_approval(OnRequest)
160 ;; returns false, so no escalation --
161 ;; denial returned directly.
162 ;; 4. ToolEmitter::finish sends the error
163 ;; to the mock model.
164 ;; 5. Second mock SSE response fires,
165 ;; turn finishes with TurnComplete.
166 ;; 6. Op::Interrupt arrives 100 ms later,
167 ;; but the turn is already done --
168 ;; TurnAborted is never emitted,
169 ;; test times out.
170 ;;
171 ;; The similar interrupt_long_running_tool_
172 ;; emits_turn_aborted passes because it
173 ;; sends the interrupt with no delay and
174 ;; has only one mock response (so the turn
175 ;; cannot complete first).
176 "--skip" "interrupt_persists_turn_aborted_marker_in_next_request"
177 ;; Same causal chain as above.
178 "--skip" "interrupt_tool_records_history_entries"
179 ;; WorkspaceWrite sandbox policy wraps
180 ;; commands with codex-linux-sandbox;
181 ;; Landlock panics, command never runs,
182 ;; exit_code is None instead of Some(0).
183 "--skip" "request_permissions_grants_apply_to_later_exec_command_calls"
184 ;; Same root cause.
185 "--skip" "request_permissions_grants_apply_to_later_shell_command_calls"
186 ;; Same root cause.
187 "--skip" "partial_request_permissions_grants_do_not_preapprove_new_permissions"
188 ;;; END Landlock
189 ;;; BEGIN mcp_process.rs initialize
190 ;;; assertion expects "version":"0.0.0"
191 ;;; but the server returns "0.120.0".
192 "--skip" "test_codex_tool_passes_base_instructions"
193 "--skip" "test_shell_command_approval_triggers_elicitation"
194 "--skip" "test_patch_approval_triggers_elicitation"
195 ;;; END version mismatch
196 ;;; BEGIN Test isolation bug:
197 ;;; unique_temp_dir() in
198 ;;; state/src/runtime.rs names dirs
199 ;;; using nanosecond timestamps, so
200 ;;; parallel tests can collide. The
201 ;;; SQLite migration runs CREATE TABLE
202 ;;; threads (without IF NOT EXISTS),
203 ;;; so the second init panics with
204 ;;; "table threads already exists".
205 "--skip" "init_removes_legacy_state_db_files"
206 "--skip" "upsert_and_get_thread_memory"
207 "--skip" "get_last_n_thread_memories_for_cwd_matches_exactly"
208 "--skip" "upsert_thread_memory_errors_for_unknown_thread"
209 "--skip" "get_last_n_thread_memories_for_cwd_zero_returns_empty"
210 "--skip" "get_last_n_thread_memories_for_cwd_does_not_prefix_match"
211 "--skip" "deleting_thread_cascades_thread_memory"
212 ;;; END SQLite test isolation
213 ;;; BEGIN unchanged filesystem mtime
214 ;; This test overwrites an imported
215 ;; session immediately and expects
216 ;; detection to hash it again. When
217 ;; both writes receive the same mtime,
218 ;; the mtime fast path treats the source
219 ;; as unchanged and returns no session.
220 "--skip" "redetects_sessions_when_source_contents_change_after_import"
221 ;;; END unchanged filesystem mtime
222 ;;; BEGIN bare 'sh' after env_clear().
223 ;; These tests spawn 'sh' after
224 ;; env_clear() with an empty env map;
225 ;; glibc execvp uses confstr(_CS_PATH)
226 ;; ("/bin:/usr/bin") when PATH is
227 ;; unset, but /bin/sh does not exist
228 ;; in the build container.
229 "--skip" "cancellation_expiration_keeps_process_alive_until_terminated"
230 "--skip" "timeout_or_cancellation_reports_cancellation_without_timeout_exit_code"
231 ;;; END bare 'sh' after env_clear()
232 ;;; BEGIN Guile 3.0.9 system* inherits ignored SIGINT
233 ;; Guile 3.0.9's system* temporarily sets
234 ;; SIGINT to SIG_IGN while it waits for the
235 ;; command ("Make sure the child can't kill
236 ;; us"). The test command inherits that
237 ;; disposition through cargo, and Codex's
238 ;; pipe backend does not reset it before
239 ;; exec. Its shell SIGINT trap cannot run.
240 "--skip" "exec_process_signal_interrupts_process"
241 ;;; END Guile 3.0.9 system* inherits ignored SIGINT
242 ;; Proxy baseline_policy returns 403
243 ;; "not_allowed_local" for example.com;
244 ;; test expects 200.
245 "--skip" "managed_network_proxy_decider_survives_full_access_start"
246 ;;; BEGIN bash_snapshot_script() uses
247 ;;; "compgen -e" to list exports but
248 ;;; bash-minimal lacks programmable
249 ;;; completion; export section is empty.
250 ;; stdout.contains("VALID_NAME") fails.
251 "--skip" "bash_snapshot_filters_invalid_exports"
252 ;; stdout.contains("MULTILINE_CERT") fails.
253 "--skip" "bash_snapshot_preserves_multiline_exports"
254 ;; snapshot.contains("PATH") fails.
255 "--skip" "linux_bash_snapshot_includes_sections"
256 ;;; END compgen
257 ;; substitute* patches PAGER to store
258 ;; path of cat; test expects bare "cat".
259 "--skip" "unified_exec_env_injects_defaults"
260 ;;; BEGIN V8 disabled (codex-code-mode
261 ;;; default-features = false).
262 ;; codex-core: missing process-host fallback
263 ;; executes JavaScript in the V8 session.
264 "--skip" "missing_process_host_falls_back_to_in_process_session"
265 ;; codex-core: wait tracing terminates a cell
266 ;; through the code-mode runtime.
267 "--skip" "missing_code_mode_wait_traces_only_the_wait_tool_call"
268 "--skip" "suite::code_mode::"
269 "--skip" "suite::code_mode_elicitation::"
270 "--skip" "suite::v2::imagegen_extension::standalone_image_generation_is_callable_from_code_mode_only"
271 ;;; END V8 disabled
272 ;;; BEGIN same compgen root cause:
273 ;;; assert_posix_snapshot_sections
274 ;;; asserts snapshot.contains("PATH").
275 ;;; END compgen / shell snapshot
276 ;;; BEGIN inaccessible snapshot under sh
277 ;;; bash-minimal lacks "compgen -e", so the
278 ;;; generated snapshot has no PATH section
279 ;;; and these tests fail their snapshot
280 ;;; assertions. They also expose a separate
281 ;;; restricted-bwrap failure.
282 "--skip" "linux_shell_command_uses_shell_snapshot"
283 "--skip" "linux_unified_exec_uses_shell_snapshot"
284 "--skip" "shell_command_snapshot_still_intercepts_apply_patch"
285 ;;; END inaccessible snapshot under sh
286 ;;; BEGIN These sandbox tests set
287 ;;; exclude_slash_tmp: true which makes
288 ;;; bwrap exclude /tmp from its mount
289 ;;; namespace. Since Guix builds under
290 ;;; /tmp, the codex-linux-sandbox binary
291 ;;; becomes invisible to bwrap even though
292 ;;; the test adds the helper dir as a
293 ;;; filesystem carve-out -- the parent /tmp
294 ;;; bind-mount is absent so the carve-out
295 ;;; has nothing to carve out of.
296 "--skip" "sandbox_reenables_writable_subpaths_under_unreadable_parents"
297 "--skip" "managed_proxy_mode_routes_through_bridge_and_blocks_direct_egress"
298 ;;; END exclude_slash_tmp
299 ;; Expects 3 TerminalInteraction events
300 ;; across timed poll windows; under CPU
301 ;; contention events merge into 2.
302 "--skip" "unified_exec_terminal_interaction_captures_delayed_output"
303 ;;; BEGIN assert_parallel_duration
304 ;;; requires < 1600 ms for parallel ops.
305 ;; Two 300 ms test_sync_tool calls.
306 "--skip" "read_file_tools_run_in_parallel"
307 ;; Two "sleep 0.25" shell commands.
308 "--skip" "shell_tools_run_in_parallel"
309 ;; One test_sync_tool + one shell sleep.
310 "--skip" "mixed_parallel_tools_run_in_parallel"
311 ;;; END assert_parallel_duration
312 ;; Asserts elapsed >= 4500 ms and
313 ;; < 5800 ms for a 5 s timeout; under
314 ;; contention elapsed drifts past 5800.
315 "--skip" "remote_models_request_times_out_after_5s"
316 ;;; BEGIN delayed auto-environment filesystem probes
317 ;; This test adds a one-second delay in each
318 ;; direction to every exec-server WebSocket
319 ;; RPC, but allows only 60 seconds for
320 ;; thread/start. Auto-environment startup
321 ;; serially probes .git, AGENTS files, and
322 ;; plugin metadata through many ancestors;
323 ;; each missing-path request/response costs
324 ;; about two seconds, so it cannot finish.
325 "--skip" "builder_interposes_fixed_delay_for_auto_env"
326 ;;; END delayed auto-environment filesystem probes
327 ;; Queued inter-agent mail must be
328 ;; injected between the reasoning/
329 ;; commentary item and the gate release;
330 ;; under contention the gate fires before
331 ;; the mail is processed, so the snapshot
332 ;; shows the stale function_call instead
333 ;; of the expected mail message.
334 "--skip" "queued_inter_agent_mail_triggers_follow_up_after_commentary_message_item"
335 ;; Same root cause: snapshot mismatch at
336 ;; pending_input.rs assert_two_responses_
337 ;; input_snapshot; expects mail at position
338 ;; 04 but gets function_call/shell instead.
339 "--skip" "queued_inter_agent_mail_triggers_follow_up_after_reasoning_item"
340 ;;; BEGIN tracing callsite cache race
341 ;; These tests install a thread-local tracing
342 ;; subscriber, then rebuild tracing's
343 ;; process-global callsite-interest cache.
344 ;; Parallel tests can disable the request span,
345 ;; causing the parent trace context to be sent.
346 "--skip" "process_start_propagates_caller_trace_context_across_background_task"
347 "--skip" "rpc_client_propagates_current_trace_context"
348 ;;; END tracing callsite cache race
349 ;;; BEGIN DNS for hostnames like
350 ;;; example.com fails in the build
351 ;;; sandbox. host_resolves_to_non_
352 ;;; public_ip() in runtime.rs treats
353 ;;; failed DNS as non-public and
354 ;;; returns NotAllowedLocal, so tests
355 ;;; expecting Allowed or Decider get
356 ;;; Deny/BaselinePolicy instead.
357 "--skip" "host_blocked_requires_allowlist_match"
358 "--skip" "add_allowed_domain_removes_matching_deny_entry"
359 "--skip" "host_blocked_subdomain_wildcards_exclude_apex"
360 "--skip" "host_blocked_global_wildcard_allowlist_allows_public_hosts_except_denylist"
361 "--skip" "evaluate_host_policy_emits_domain_event_for_decider_ask"
362 "--skip" "evaluate_host_policy_emits_domain_event_for_decider_allow_override"
363 "--skip" "http_connect_accept_blocks_in_limited_mode"
364 "--skip" "http_connect_accept_allows_allowlisted_host_in_full_mode"
365 "--skip" "mitm_policy_blocks_disallowed_method_and_records_telemetry"
366 "--skip" "http_connect_accept_blocks_hooked_host_in_full_mode_without_mitm_state"
367 "--skip" "http_connect_accept_defers_brokered_host_mitm_until_protocol_detection"
368 "--skip" "http_connect_accept_passes_environment_id_to_decider"
369 "--skip" "mitm_policy_allows_matching_hooked_write_in_full_mode"
370 "--skip" "mitm_policy_blocks_hook_miss_for_hooked_host_and_records_telemetry_in_full_mode"
371 "--skip" "mitm_policy_blocks_matching_hooked_write_in_limited_mode"
372 "--skip" "evaluate_host_policy_emits_execution_id_for_baseline_allow"
373 "--skip" "handle_socks5_tcp_blocks_hooked_non_https_host_in_full_mode"
374 "--skip" "handle_socks5_tcp_blocks_limited_mode_without_mitm_state"
375 "--skip" "handle_socks5_tcp_detects_tls_for_brokered_nonstandard_port_in_full_mode"
376 "--skip" "handle_socks5_tcp_uses_mitm_for_hooked_host_in_full_mode"
377 "--skip" "handle_socks5_tcp_uses_mitm_in_limited_mode"
378 ;; These tests bind their servers to
379 ;; 127.0.0.1 but construct WebSocket URLs
380 ;; using the hostname "localhost".
381 ;; connect_tcp() passes that hostname to
382 ;; tokio::net::lookup_host, which requires
383 ;; a name resolver unavailable in the
384 ;; build container.
385 "--skip" "public_connector_uses_factory_and_exposes_stream_and_sink"
386 "--skip" "direct_route_connects_secure_websocket"
387 "--skip" "http_proxy_tunnels_secure_websocket_before_handshake"
388 "--skip" "https_proxy_tunnels_secure_websocket_before_handshake"
389 ;;; END DNS failure / NotAllowedLocal
390 ;;; BEGIN Flaky: wiremock mock expects
391 ;;; 1 POST to /codex/safety/arc but
392 ;;; receives 0. Passes in some builds,
393 ;;; fails in others with identical code
394 ;;; and env. The identical custom_
395 ;;; variant always passes; only the
396 ;;; CODEX_APPS_MCP_SERVER_NAME variant
397 ;;; flakes, suggesting a timing race
398 ;;; in the async POST under contention.
399 "--skip" "approve_mode_blocks_when_arc_returns_interrupt_for_model"
400 "--skip" "approve_mode_blocks_when_arc_returns_interrupt_without_annotations"
401 ;;; END ARC monitor flake
402 ;;; BEGIN nss-certs sets SSL_CERT_FILE
403 ;;; which makes build_reqwest_client load
404 ;;; 171 CA certs via add_root_certificate.
405 ;;; This changes the reqwest TLS backend
406 ;;; configuration; the ARC safety POST to
407 ;;; the localhost mock server never arrives
408 ;;; (wiremock expects 1 request, gets 0).
409 "--skip" "approve_mode_blocks_when_arc_returns_interrupt_for_model"
410 "--skip" "approve_mode_blocks_when_arc_returns_interrupt_without_annotations"
411 ;;; END nss-certs / ARC monitor
412 ;; Upstream bug: JsonRpcConnection has
413 ;; no websocket keepalive or read timeout.
414 ;; When the exec-server is killed, the
415 ;; client's reader task blocks on
416 ;; websocket_reader.next() forever because
417 ;; TCP doesn't detect the dead peer without
418 ;; a write or keepalive probe. Previously
419 ;; masked because empty env made "sleep 10"
420 ;; fail instantly; with PATH the process
421 ;; lives long enough to hit the deadlock.
422 "--skip" "remote_exec_process_reports_transport_disconnect"
423 ;; 500 ms timeout on MCP list response
424 ;; (mcp_server_status.rs timeout at
425 ;; Duration::from_millis(500)). With
426 ;; nss-certs TLS now works, so the
427 ;; startup plugin sync HTTP requests
428 ;; (chatgpt.com, api.github.com) wait
429 ;; for TCP timeout instead of failing
430 ;; instantly at TLS, eating the budget.
431 "--skip" "mcp_server_status_list_tools_and_auth_only_skips_slow_inventory_calls"
432 ;;; BEGIN Stack overflow in 'current_thread'
433 ;;; tokio runtime. The sibling tests in
434 ;;; tracing_tests.rs use the local helper
435 ;;; run_current_thread_test_with_stack
436 ;;; (4 MiB) but this one uses bare
437 ;;; #[tokio::test] on the 2 MiB default
438 ;;; test-thread stack, and TurnStart's
439 ;;; future graph exceeds it. With
440 ;;; RUST_MIN_STACK raised the overflow is
441 ;;; gone but the test then deterministically
442 ;;; times out waiting for a span tagged
443 ;;; codex.op = "user_input" on the remote
444 ;;; trace that upstream never emits on that
445 ;;; trace in our environment.
446 "--skip" "turn_start_jsonrpc_span_parents_core_turn_spans"
447 ;;; END tracing test stack/timeout
448 ;;; BEGIN The upstream loader resolves the
449 ;;; MCP OAuth credentials store mode by
450 ;;; calling
451 ;;; resolve_mcp_oauth_credentials_store_mode
452 ;;; with env!("CARGO_PKG_VERSION") -- which
453 ;;; in the released 0.142.0 tarball is
454 ;;; "0.142.0". These fixture tests
455 ;;; construct the expected Config by
456 ;;; passing the LOCAL_DEV_BUILD_VERSION
457 ;;; constant ("0.0.0") to the same
458 ;;; resolver, and the resolver only returns
459 ;;; File for "0.0.0" -- Auto otherwise.
460 ;;; Upstream CI builds from a checkout
461 ;;; still carrying version = "0.0.0" so the
462 ;;; tests pass there; they cannot pass
463 ;;; against a released tag.
464 "--skip" "test_precedence_fixture_with_gpt3_profile"
465 "--skip" "test_precedence_fixture_with_gpt5_profile"
466 "--skip" "test_precedence_fixture_with_o3_profile"
467 "--skip" "test_precedence_fixture_with_zdr_profile"
468 ;;; END LOCAL_DEV_BUILD_VERSION fixture mismatch
469 ;;; BEGIN Verifies that Codex's user-shell
470 ;;; spawn produces a child where
471 ;;; CODEX_SANDBOX_NETWORK_DISABLED is unset
472 ;;; (asserts stdout == "not-set"). We
473 ;;; deliberately set that env var at the
474 ;;; check phase to trigger upstream's
475 ;;; skip_if_no_network! guards; the var then
476 ;;; leaks into the spawned shell and the
477 ;;; assertion fails. The guix skip is a
478 ;;; direct cost of the network-gate fix --
479 ;;; trading one broken test for two that
480 ;;; now skip cleanly.
481 "--skip" "user_shell_command_does_not_set_network_sandbox_env_var"
482 ;;; END CODEX_SANDBOX_NETWORK_DISABLED leak
483 ;;; BEGIN The exec-server/tests/file_system
484 ;;; integration tests invoke the real
485 ;;; system bwrap, which tries to execvp the
486 ;;; test binary at the cargo target path
487 ;;; under the build dir. Same root cause
488 ;;; as the existing
489 ;;; sandbox_reenables_writable_subpaths_under_unreadable_parents
490 ;;; skip: guix builds live under /tmp but
491 ;;; bwrap's mount namespace excludes /tmp,
492 ;;; making the binary invisible inside the
493 ;;; namespace.
494 "--skip" "file_system_copy_preserves_symlink_source"
495 "--skip" "file_system_copy_rejects_symlink_escape_destination"
496 "--skip" "file_system_copy_rejects_symlink_escape_source"
497 "--skip" "file_system_create_directory_rejects_symlink_escape"
498 "--skip" "file_system_read_directory_rejects_symlink_escape"
499 "--skip" "file_system_remove_rejects_symlink_escape"
500 "--skip" "file_system_remove_removes_symlink_not_target"
501 "--skip" "file_system_sandboxed_read_allows_readable_root"
502 "--skip" "file_system_sandboxed_read_rejects_symlink_escape"
503 "--skip" "file_system_sandboxed_read_rejects_symlink_parent_dotdot_escape"
504 "--skip" "file_system_sandboxed_write_allows_additional_write_root"
505 "--skip" "file_system_sandboxed_write_rejects_symlink_escape"
506 "--skip" "file_system_sandboxed_write_rejects_unwritable_path"
507 ;;; END bwrap-cant-see-/tmp file_system tests
508 ;;; BEGIN Landlock is unavailable on Guix,
509 ;;; so the sandbox cannot deny network
510 ;;; syscalls. These tests expect
511 ;;; SandboxErr::Denied for the nc, ping,
512 ;;; ssh, getent, and dev_tcp_redirection
513 ;;; commands but those commands instead
514 ;;; block on connect() until the test's 2s
515 ;;; timeout fires (Timeout, exit 124).
516 ;;; Same root cause as the Landlock skips
517 ;;; at the top of this list; newer test
518 ;;; names.
519 "--skip" "sandbox_blocks_nc"
520 "--skip" "sandbox_blocks_ping"
521 "--skip" "sandbox_blocks_dev_tcp_redirection"
522 "--skip" "sandbox_blocks_getent"
523 "--skip" "sandbox_blocks_ssh"
524 ;;; END Landlock network block
525 ;;; BEGIN Requires github.com network access.
526 ;;; With no "extraKnownMarketplaces" entry
527 ;;; in settings.json,
528 ;;; collect_marketplace_import_sources
529 ;;; inserts a fallback entry for the
530 ;;; "claude-plugins-official" marketplace
531 ;;; pointing at the github shorthand
532 ;;; "anthropics/claude-plugins-official".
533 ;;; add_marketplace -> parse_marketplace_source
534 ;;; expands that to a github.com/.git URL
535 ;;; and clone_git_source runs 'git clone'.
536 ;;; The Guix build sandbox has no network,
537 ;;; so the clone fails and the marketplace
538 ;;; lands in failed_marketplaces. No mock
539 ;;; cloner is injected via
540 ;;; ExternalAgentConfigService::new_for_test,
541 ;;; so there is no in-tree way to satisfy
542 ;;; the test offline.
543 "--skip" "import_plugins_infers_external_official_marketplace_when_missing_from_settings"
544 ;;; END github.com network access
545 ;;; BEGIN Async race between the rollout
546 ;;; writer task and a direct SQLite read.
547 ;;; The test calls
548 ;;; update_memory_settings_with_app_server,
549 ;;; which routes through the in-memory
550 ;;; thread path on the app-server's
551 ;;; ThreadMemoryModeSet handler ->
552 ;;; Session::set_thread_memory_mode ->
553 ;;; persist_thread_memory_mode_update,
554 ;;; which writes a RolloutItem::SessionMeta
555 ;;; with the new mode to the rollout file
556 ;;; via recorder.record_items + flush. The
557 ;;; rollout writer task then asynchronously
558 ;;; calls sync_thread_state_after_write,
559 ;;; which calls state_db::apply_rollout_items
560 ;;; to mirror the SessionMeta into SQLite.
561 ;;; flush() only awaits the rollout-file
562 ;;; write, not the subsequent SQLite sync.
563 ;;; The test opens a fresh StateRuntime and
564 ;;; reads get_thread_memory_mode
565 ;;; immediately, so it observes the creation
566 ;;; default of "enabled" written by
567 ;;; upsert_thread_with_creation_memory_mode
568 ;;; instead of the new "disabled". Upstream
569 ;;; CI wins this race; we lose.
570 "--skip" "update_memory_settings_updates_current_thread_memory_mode"
571 ;;; END memory-mode rollout/sqlite race
572 ;;; BEGIN The Stopwatch::new constructor in
573 ;;; codex_shell_escalation anchors
574 ;;; T0 = Instant::now() at construction;
575 ;;; the spawned cancellation task fires at
576 ;;; T0 + limit. The test captures
577 ;;; start = Instant::now() at T1 > T0 (after
578 ;;; cancellation_token() returns) and
579 ;;; asserts start.elapsed() >= limit,
580 ;;; i.e. (T0 + limit) - T1 >= limit, which
581 ;;; is always false by the offset T1 - T0.
582 ;;; The test only passes when scheduler
583 ;;; jitter on the sleep wakeup happens to
584 ;;; exceed that offset. Upstream wins the
585 ;;; race; we don't always.
586 "--skip" "cancellation_receiver_fires_after_limit"
587 ;;; END Stopwatch construction/start offset race
588 ;;; BEGIN invalid remote-control auth-wakeup test
589 ;;; remote_control_waits_for_account_id_before_enrolling
590 ;;; tries to prove that auth_manager.reload()
591 ;;; wakes the remote-control worker before
592 ;;; REMOTE_CONTROL_ACCOUNT_ID_RETRY_INTERVAL.
593 ;;; It does this by first waiting 100 ms for
594 ;;; "no request", then rewriting auth.json,
595 ;;; calling reload(), and waiting another
596 ;;; 100 ms for the enroll request.
597 ;;;
598 ;;; That is not a valid assertion of the
599 ;;; intended behavior. The retry sleep starts
600 ;;; when the worker first observes the missing
601 ;;; account id, before the test's first
602 ;;; 100 ms wait completes. A larger second
603 ;;; timeout can therefore overlap the existing
604 ;;; one-second fallback retry and pass even if
605 ;;; the auth-change watch never woke the task.
606 ;;; The test also has no synchronization point
607 ;;; proving the worker is actually parked on
608 ;;; auth_change_rx.changed() before reload().
609 "--skip" "remote_control_waits_for_account_id_before_enrolling"
610 ;;; END invalid remote-control auth-wakeup test
611 ;;; BEGIN invalid remote-control timeout tests
612 ;;; These are tests, not production behavior.
613 ;;; request_timeout_before_response_headers_is_transient
614 ;;; tries to prove that a reqwest timeout before
615 ;;; response headers is classified as a
616 ;;; transient remote-control server error.
617 ;;; response_body_timeout_is_transient tries to
618 ;;; prove the same classification for a timeout
619 ;;; while reading the response body after a
620 ;;; partial HTTP 200 response.
621 ;;;
622 ;;; Both tests use timed_out_request. That
623 ;;; helper starts a fake TCP server and races
624 ;;; the intended server setup against a 100 ms
625 ;;; client timeout. The client is supposed to
626 ;;; time out; the invalid test assumption is
627 ;;; that the spawned fake server task has always
628 ;;; reached the later wait point before that
629 ;;; timeout fires. For the header-timeout test
630 ;;; that means after listener.accept(); for the
631 ;;; body-timeout test that means after
632 ;;; listener.accept() and
633 ;;; stream.write_all(partial_response).
634 ;;;
635 ;;; If the client times out before the fake
636 ;;; server reaches that point, timed_out_request
637 ;;; has already received the timeout error the
638 ;;; test wants to assert. It then tries to
639 ;;; clean up by sending request_done_tx and
640 ;;; awaiting server_task. But request_done_tx
641 ;;; is only observed after the fake server has
642 ;;; completed the accept/partial-response setup;
643 ;;; it cannot wake listener.accept() or an
644 ;;; earlier write. server_task.await can
645 ;;; therefore hang before either test reaches
646 ;;; assert_transient_timeout.
647 "--skip" "request_timeout_before_response_headers_is_transient"
648 "--skip" "response_body_timeout_is_transient"
649 ;;; END invalid remote-control timeout tests
650 ;;; BEGIN Order-brittle thread-name websocket
651 ;;; notification test.
652 ;;; thread_name_updated_broadcasts_for_loaded_threads
653 ;;; resumes a stored thread, reads only the
654 ;;; thread/resume response, renames the thread,
655 ;;; reads the thread/name/updated notifications,
656 ;;; and then asserts both websocket clients are
657 ;;; silent.
658 ;;;
659 ;;; The resume path can emit an additional
660 ;;; thread/goal/cleared snapshot notification
661 ;;; after the resume response when the resumed
662 ;;; thread has no stored goal.
663 ;;; The test does not drain or expect that
664 ;;; valid post-resume notification, so the
665 ;;; final silence assertion can fail on
666 ;;; thread/goal/cleared even though the thread
667 ;;; rename broadcast itself succeeded.
668 "--skip" "thread_name_updated_broadcasts_for_loaded_threads"
669 ;;; END Order-brittle thread-name websocket notification test
670 ;;; BEGIN Order-brittle app-list notification
671 ;;; tests. Codex fetches its apps list from
672 ;;; two sources in parallel (installed apps
673 ;;; via MCP, available apps via HTTP)
674 ;;; and emits an "app/list/updated"
675 ;;; notification as each source finishes.
676 ;;; The tests use server-side delays
677 ;;; (0 ms for MCP, 300 ms for HTTP) to force
678 ;;; the MCP source to finish first, then
679 ;;; assert a strict two-notification
680 ;;; sequence.
681 ;;; The implementation is order-agnostic
682 ;;; (apps_processor.rs suppresses the
683 ;;; HTTP-only interim; final merged
684 ;;; state is identical either way).
685 ;;; When the HTTP source wins the race, only
686 ;;; the final merged notification fires, so
687 ;;; the test sees one notification instead of
688 ;;; two and fails the assertion against the
689 ;;; expected first (accessible-only)
690 ;;; notification.
691 "--skip" "list_apps_emits_updates_and_returns_after_both_lists_load"
692 "--skip" "list_apps_force_refetch_patches_updates_from_cached_snapshots"
693 ;;; END Order-brittle app-list notification tests
694 ;;; BEGIN Skip .git creators. Otherwise other
695 ;;; 8 tests would have their context poisoned.
696 ;;; The other tests care about .git not being
697 ;;; there.
698 "--skip" "turn_interrupt_aborts_running_turn"
699 "--skip" "sandbox_with_network_proxy_blocks_direct_loopback_access"
700 "--skip" "zsh_fork_inner_allowed_script_inherits_active_permission_profile"
701 "--skip" "permission_request_hook_allows_network_approval_without_prompt"
702 "--skip" "guardian_receives_exact_triggers_for_concurrent_network_requests"
703 "--skip" "guardian_receives_exact_trigger_for_single_network_request"
704 "--skip" "approved_network_host_for_one_environment_still_prompts_in_another"
705 "--skip" "unified_exec_network_denial_emits_failed_background_end_event"
706 "--skip" "unified_exec_short_lived_network_denial_emits_failed_end_event"
707 ;;; END Skip .git creators.
708 )
709 #:phases
710 #~(modify-phases %standard-phases
711 (add-after 'unpack 'chdir-to-workspace
712 (lambda _
713 (chdir "codex-rs")))
714 (add-after 'chdir-to-workspace 'update-version-in-snapshots
715 (lambda _
716 ;; Snapshot test files contain hardcoded 0.0.0 version strings.
717 ;; Update them to match the actual package version.
718 (let ((snap-files (find-files "." "\\.snap$")))
719 (substitute* snap-files
720 (("\\(v0\\.0\\.0\\) ") "(v0.144.1) ")
721 (("Update available! 0\\.0\\.0 -> 9\\.9\\.9 ")
722 "Update available! 0.144.1 -> 9.9.9")
723 (("Update available! 0\\.0\\.0 -> 9\\.9\\.9")
724 "Update available! 0.144.1 -> 9.9.9")))))
725 (add-after 'chdir-to-workspace 'patch-git-deps-to-vendor
726 (lambda _
727 ;; Replace git dependencies with version references so cargo
728 ;; resolves them from the vendored sources.
729 (substitute* "Cargo.toml"
730 (("crossterm = \\{ git = [^}]+\\}")
731 "crossterm = { version = \"0.28.1\" }")
732 (("ratatui = \\{ git = [^}]+\\}")
733 "ratatui = { version = \"0.29.0\" }")
734 (("tokio-tungstenite = \\{ git = [^}]+\\}")
735 "tokio-tungstenite = { version = \"0.28.0\" }")
736 (("tungstenite = \\{ git = [^}]+\\}")
737 "tungstenite = { version = \"0.27.0\" }")
738 (("nucleo = \\{ git = [^}]+\\}")
739 "nucleo = { version = \"0.5.0\" }")
740 (("runfiles = \\{ git = [^}]+\\}")
741 "runfiles = { version = \"0.1.0\" }"))
742 ;; Remove workspace members that have unbuildable deps
743 ;; (v8-poc requires V8). code-mode stays a workspace member
744 ;; so its codex-protocol resolves to the same local copy as
745 ;; the rest of the build; disabling its default features
746 ;; (below) avoids pulling in V8.
747 (substitute* "Cargo.toml"
748 (("\"v8-poc\",") ""))
749 ;; Disable V8 runtime in codex-code-mode by turning off
750 ;; default features. Keep path= so code-mode is resolved
751 ;; from the local source tree and shares codex-protocol
752 ;; (and other workspace crates) with the rest of the build;
753 ;; resolving it from the vendor instead introduces a
754 ;; duplicate codex-protocol and causes E0308 type
755 ;; mismatches in codex-tools.
756 (substitute* "Cargo.toml"
757 (("codex-code-mode = \\{ path = \"code-mode\" \\}")
758 "codex-code-mode = { path = \"code-mode\", default-features = false }"))))
759 (add-after 'patch-git-deps-to-vendor 'add-version-to-workspace-deps
760 (lambda _
761 ;; Keep internal workspace path dependencies local, while giving
762 ;; Cargo explicit versions for resolver/vendor normalization.
763 (let ((cargo-files (find-files "." "^Cargo\\.toml$")))
764 (substitute* cargo-files
765 ;; Handle inline deps: name = { path = "..." }
766 (("(codex[_-][a-z0-9_-]+|app_test_support|core_test_support|mcp_test_support) = \\{ path = " all name)
767 (string-append name " = { version = \"0.144.1\", path = "))
768 ;; Handle inline deps with package: name = { package = "...", path = "..." }
769 (("(codex[_-][a-z0-9_-]+|app_test_support|core_test_support|mcp_test_support) = \\{ package = " all name)
770 (string-append name " = { version = \"0.144.1\", package = "))
771 ;; Handle section deps: [dependencies.X] with path = "..."
772 (("^(path = \"\\.\\./[^\"]*\")" all path-line)
773 (string-append path-line "\nversion = \"0.144.1\""))))))
774 (add-after 'chdir-to-workspace 'use-gnu-store-in-sandbox
775 (lambda _
776 ;; LINUX_PLATFORM_DEFAULT_READ_ROOTS in linux-sandbox/src/
777 ;; bwrap.rs is the read-only baseline that codex's bwrap
778 ;; sandbox bind-mounts so commands can read /usr/bin/ls,
779 ;; libc, etc. Upstream lists "/nix/store" for NixOS;
780 ;; Guix's equivalent is "/gnu/store". Without this swap,
781 ;; the codex sandbox cannot locate any binaries on a Guix
782 ;; system because every "system" path resolves into
783 ;; /gnu/store/<hash>-pkg/bin/...
784 (substitute* "linux-sandbox/src/bwrap.rs"
785 (("\"/nix/store\"") "\"/gnu/store\""))))
786 (add-after 'chdir-to-workspace 'patch-hardcoded-paths
787 (lambda* (#:key inputs #:allow-other-keys)
788 (let ((bash-bin (string-append
789 (assoc-ref inputs "bash-minimal") "/bin"))
790 (coreutils-bin (string-append
791 (assoc-ref inputs "coreutils") "/bin"))
792 (git-bin (string-append
793 (assoc-ref inputs "git-minimal") "/bin"))
794 (sed-bin (string-append
795 (assoc-ref inputs "sed") "/bin"))
796 ;; Include .policy files: the execpolicy-legacy
797 ;; crate embeds default.policy via include_str!
798 ;; at compile time, so its paths must also be
799 ;; patched.
800 (rs-files (find-files "." "\\.(rs|policy)$")))
801 (substitute* rs-files
802 (("\"/bin/bash\"")
803 (string-append "\"" bash-bin "/bash\""))
804 (("\"/bin/sh\"")
805 (string-append "\"" bash-bin "/sh\""))
806 (("\"/usr/bin/bash\"")
807 (string-append "\"" bash-bin "/bash\""))
808 (("\"/usr/bin/sh\"")
809 (string-append "\"" bash-bin "/sh\""))
810 ;;; bash/sh with inline arguments, e.g. "/bin/bash -i".
811 (("\"/bin/bash ")
812 (string-append "\"" bash-bin "/bash "))
813 (("\"/bin/sh ")
814 (string-append "\"" bash-bin "/sh "))
815 ;; Single-quoted paths in test assertion strings,
816 ;; e.g. "exec '/bin/bash' -c ...". Must match the
817 ;; double-quoted substitutions above so both sides
818 ;; of assert_eq!/contains use the store path.
819 (("'/bin/bash'")
820 (string-append "'" bash-bin "/bash'"))
821 (("'/bin/sh'")
822 (string-append "'" bash-bin "/sh'"))
823 ;; coreutils.
824 (("\"/bin/(cat|cp|date|echo|head|ls|rm|sleep|true|touch)\"" all cmd)
825 (string-append "\"" coreutils-bin "/" cmd "\""))
826 ;; coreutils.
827 (("\"/usr/bin/(cat|cp|head|ls|touch|true)\"" all cmd)
828 (string-append "\"" coreutils-bin "/" cmd "\""))
829 ;; coreutils with inline arguments
830 ;; like "/bin/echo END-EVENT".
831 (("\"/bin/(cat|cp|date|echo|head|ls|rm|sleep|true|touch) " all cmd)
832 (string-append "\"" coreutils-bin "/" cmd " "))
833 (("\"/usr/bin/git\"")
834 (string-append "\"" git-bin "/git\""))
835 (("\"/usr/bin/sed\"")
836 (string-append "\"" sed-bin "/sed\"")))
837 ;; Bare Command::new("git") and Command::new("ls")
838 ;; in codex source files that exec internally.
839 (substitute* (find-files "git-utils" "\\.rs$")
840 (("Command::new\\(\"git\"\\)")
841 (string-append "Command::new(\"" git-bin "/git\")")))
842 (substitute* "tui/src/get_git_diff.rs"
843 (("Command::new\\(\"git\"\\)")
844 (string-append "Command::new(\"" git-bin "/git\")")))
845 (substitute* "core/src/turn_diff_tracker.rs"
846 (("Command::new\\(\"git\"\\)")
847 (string-append "Command::new(\"" git-bin "/git\")")))
848 (substitute* "cloud-tasks/src/env_detect.rs"
849 (("Command::new\\(\"git\"\\)")
850 (string-append "Command::new(\"" git-bin "/git\")")))
851 (substitute* "core-plugins/src/startup_sync.rs"
852 (("\"git\",")
853 (string-append "\"" git-bin "/git\",")))
854 (substitute* "file-search/src/lib.rs"
855 (("Command::new\\(\"ls\"\\)")
856 (string-append "Command::new(\"" coreutils-bin "/ls\")")))
857 ;; Neutralize pager with absolute path to cat.
858 (substitute* "core/src/unified_exec/process_manager.rs"
859 (("\"PAGER\", \"cat\"")
860 (string-append "\"PAGER\", \"" coreutils-bin "/cat\""))
861 (("\"GIT_PAGER\", \"cat\"")
862 (string-append "\"GIT_PAGER\", \"" coreutils-bin "/cat\""))
863 (("\"GH_PAGER\", \"cat\"")
864 (string-append "\"GH_PAGER\", \"" coreutils-bin "/cat\"")))
865 ;; Bare /bin/sleep in the exec-server test (without
866 ;; surrounding double quotes, so the main pattern does
867 ;; not match).
868 (substitute*
869 "exec-server/tests/exec_process.rs"
870 (("/bin/sleep")
871 (string-append coreutils-bin "/sleep")))
872 ;; The encrypted-relay test clears the child
873 ;; environment, so its bare "true" has no PATH
874 ;; through which it could be resolved.
875 (substitute*
876 "exec-server/tests/relay.rs"
877 (("\"true\"")
878 (string-append "\"" coreutils-bin "/true\"")))
879 ;; Bare /bin/sleep inside a format! string
880 ;; (no surrounding double quotes, so the main
881 ;; pattern does not match).
882 (substitute*
883 "shell-escalation/src/unix/escalate_server.rs"
884 (("/bin/sleep")
885 (string-append coreutils-bin "/sleep")))
886 ;; @SHELL@ placeholder from test-shebangs patch
887 (substitute*
888 (list "rmcp-client/src/program_resolver.rs"
889 "tui/src/external_editor.rs")
890 (("@SHELL@")
891 (string-append bash-bin "/sh")))
892 ;; Shebangs inside raw string literals (r#"..."#)
893 ;; that are written to scripts at runtime. The
894 ;; main "/bin/sh" patterns only match double-quoted
895 ;; occurrences; these shebangs have no surrounding
896 ;; double quotes.
897 (substitute*
898 (list "cli/src/doctor.rs"
899 "core-plugins/src/npm_source_tests.rs"
900 "core-plugins/src/remote_bundle.rs"
901 "core-plugins/src/startup_sync_tests.rs"
902 "core/src/git_info_tests.rs"
903 "core/src/tools/runtimes/shell/unix_escalation_tests.rs"
904 "core/tests/suite/client.rs"
905 "core/tests/suite/guardian_review.rs"
906 "core/tests/suite/skill_approval.rs"
907 "core/tests/suite/user_notification.rs"
908 "exec-server/tests/file_system_unix.rs"
909 "git-utils/src/baseline.rs"
910 "git-utils/src/info.rs"
911 "login/src/auth/auth_tests.rs"
912 "models-manager/src/manager_tests.rs"
913 "sandboxing/src/bwrap_tests.rs"
914 "shell-command/src/command_safety/is_safe_command.rs"
915 "tui/src/get_git_diff.rs")
916 (("#!/bin/bash")
917 (string-append "#!" bash-bin "/bash"))
918 (("#!/bin/sh")
919 (string-append "#!" bash-bin "/sh")))
920 ;; Test helpers pass env: Default::default() to the
921 ;; spawn code which calls env_clear(), leaving
922 ;; processes with no PATH. Inject the build-time
923 ;; PATH so tests find coreutils, bash, etc.
924 (substitute* (find-files "." "\\.rs$")
925 (("env: Default::default\\(\\)")
926 (string-append
927 "env: std::env::var(\"PATH\")"
928 ".into_iter()"
929 ".map(|p| (\"PATH\".to_string(), p))"
930 ".collect()")))
931 ;; Test-only absolute path; bare name works via PATH.
932 (substitute* "exec-server/tests/exec_process.rs"
933 (("\"/usr/bin/python3\"")
934 "\"python3\""))
935 ;; Same empty-env issue but in JSON literals
936 ;; (serde_json::json! macro).
937 (substitute* "exec-server/tests/process.rs"
938 (("\"env\": \\{\\}")
939 (string-append
940 "\"env\": {\"PATH\": \""
941 (getenv "PATH")
942 "\"}"))))))
943 (add-before 'build 'set-bubblewrap-source
944 (lambda* (#:key inputs #:allow-other-keys)
945 (setenv "CODEX_BWRAP_SOURCE_DIR"
946 (string-append
947 (assoc-ref inputs "bubblewrap-source")
948 "/"))))
949 (add-before 'build 'create-node-version-file
950 (lambda _
951 (call-with-output-file "guix-vendor/node-version.txt"
952 (lambda (port)
953 (display "22.22.0" port)))))
954 (add-before 'check 'increase-open-file-limit
955 (lambda _
956 ;; The app-server integration suite runs hundreds of Tokio
957 ;; runtimes, mock HTTP servers, and app-server subprocesses under
958 ;; libtest. On large builders, the default soft nofile limit of
959 ;; 1024 is not enough and failures cascade as EMFILE.
960 (call-with-values (lambda () (getrlimit 'nofile))
961 (lambda (soft hard)
962 (let ((target (if hard (min hard 65536) 65536)))
963 (when (and soft (< soft target))
964 (setrlimit 'nofile target hard)
965 (format #t
966 "increased maximum number of open files from ~d to ~d~%"
967 soft target)))))))
968 (add-before 'check 'set-home
969 (lambda _
970 ;; HOME must not be a prefix of /tmp, otherwise
971 ;; the TUI's path shortener replaces /tmp/... with
972 ;; ~/... and 30 snapshot tests fail.
973 (setenv "HOME" "/tmp/guix-home")
974 (mkdir-p "/tmp/guix-home")
975 (setenv "USER" "nixbld")
976 ;; fetch_ide_context_uses_unregistered_request_route:
977 ;; IDE-context IPC rejects sockets in group-writable
978 ;; temporary directories.
979 (umask #o077)
980 ;; Default libtest thread stack is 2 MiB, which is not
981 ;; enough for tokio current_thread tests that drive
982 ;; codex-core's full turn pipeline. Upstream gates such
983 ;; tests through run_current_thread_test_with_stack (4 MiB)
984 ;; in app-server/src/message_processor/tracing_tests.rs;
985 ;; raise the global default so future additions that
986 ;; forget the wrapper still pass.
987 (setenv "RUST_MIN_STACK" "8388608")
988 ;; Prevent fanout on huge servers.
989 (setenv "TOKIO_WORKER_THREADS" "3")
990 ;; Set Codex's no-network marker so upstream tests using
991 ;; skip_if_no_network! return early.
992 (setenv "CODEX_SANDBOX_NETWORK_DISABLED" "1")))
993 (replace 'check
994 (lambda* (#:key tests? parallel-build? cargo-test-flags
995 #:allow-other-keys)
996 (when tests?
997 ;; Do not let libtest use all build cores here. Most
998 ;; app-server integration tests start a real app-server
999 ;; process, and each initialized server creates two
1000 ;; notify/inotify watchers (SkillsWatcher and FsWatchManager).
1001 ;; Linux's default fs.inotify.max_user_instances is often 128
1002 ;; per real UID, so --test-threads=128 can exhaust that
1003 ;; per-user inotify instance limit even after raising
1004 ;; RLIMIT_NOFILE.
1005 (let ((test-threads (min 64 (parallel-job-count))))
1006 (apply invoke
1007 `("cargo" "test" "--offline"
1008 ,@(if parallel-build?
1009 (list "-j"
1010 (number->string
1011 (parallel-job-count)))
1012 (list "-j" "1"))
1013 ,@cargo-test-flags
1014 ,@(if (member "--" cargo-test-flags)
1015 '()
1016 '("--"))
1017 "--test-threads"
1018 ,(number->string test-threads))))))))))
1019 (native-inputs `(("bubblewrap" ,bubblewrap) ;tests need bwrap on PATH
1020 ("clang" ,clang)
1021 ("cmake-minimal" ,cmake-minimal)
1022 ("curl" ,curl) ;local loopback test
1023 ("libunwind" ,libunwind)
1024 ("lsof" ,lsof) ;app-server tests
1025 ("nss-certs-for-test" ,nss-certs-for-test) ;OTLP gRPC TLS
1026 ("perl" ,perl)
1027 ("procps" ,procps) ;tests use ps
1028 ("python-minimal" ,python-minimal)
1029 ("pkg-config" ,pkg-config)
1030 ("bubblewrap-source" ,(package-source bubblewrap))))
1031 (inputs (cons* bash-minimal coreutils git-minimal sed
1032 libcap oniguruma openssl sqlite zlib `(,zstd "lib")
1033 (cargo-inputs 'codex)))
1034 (properties '((timeout . 108000) ; 30 hours
1035 (max-silent-time . 54000))) ; 15 hours
1036 (home-page "https://github.com/openai/codex")
1037 (synopsis "AI-assisted coding CLI and TUI")
1038 (description
1039 "Codex is an AI-powered coding assistant that runs in the terminal.
1040It provides an interactive TUI for conversations with AI models, with
1041support for shell command execution, file editing, and code generation.
1042Configure providers via @file{~/.codex/config.toml}.
1043
1044codex-code-mode's V8 Javascript executor is disabled. That means if
1045you want to use the GPT 5.6 models you need to add a custom
1046@code{model_catalog_json} into @code{~/.codex/config.toml} that
1047makes them NOT force code (i.e. V8) mode.")
1048 (license license:asl2.0)))
1049
1050(define-public codex-acp
1051 (package
1052 (name "codex-acp")
1053 (version "0.11.1")
1054 (source
1055 (origin
1056 (method git-fetch)
1057 (uri (git-reference
1058 (url "https://github.com/zed-industries/codex-acp")
1059 (commit (string-append "v" version))))
1060 (file-name (git-file-name name version))
1061 (sha256
1062 (base32 "1rlgkbvrbdl5jhf49sjn2mifn2rq6z0vwf0gard23y1bz6sr3wmr"))
1063 (patches (search-patches "codex-acp-0.11.1-remove-patch-sections.patch"))))
1064 (build-system cargo-build-system)
1065 (arguments
1066 (list
1067 #:install-source? #f
1068 #:phases
1069 #~(modify-phases %standard-phases
1070 (add-after 'unpack 'patch-codex-deps
1071 (lambda* (#:key inputs #:allow-other-keys)
1072 ;; Rewrite git dependencies to use vendored sources from rust-codex
1073 (substitute* "Cargo.toml"
1074 (("git = \"https://github.com/openai/codex\", tag = \"rust-v0.117.0\"")
1075 "version = \"0.117.0\""))
1076 ;; Disable V8 runtime in codex-code-mode (transitive dep).
1077 (let ((port (open-file "Cargo.toml" "a")))
1078 (display "
1079[dependencies.codex-code-mode]
1080version = \"0.117.0\"
1081default-features = false
1082" port)
1083 (close-port port))
1084 ;; Point codex-linux-sandbox at system bubblewrap source.
1085 (setenv "CODEX_BWRAP_SOURCE_DIR"
1086 (string-append
1087 (assoc-ref inputs "bubblewrap-source")
1088 "/"))))
1089 (add-before 'check 'set-home
1090 (lambda _
1091 (setenv "HOME" "/tmp")
1092 (setenv "USER" "nixbld")))
1093 (add-before 'build 'create-node-version-file
1094 (lambda _
1095 ;; codex-core's js_repl uses include_str! to read
1096 ;; node-version.txt four directories up from its source;
1097 ;; create it where the vendored crate expects it.
1098 (call-with-output-file "guix-vendor/node-version.txt"
1099 (lambda (port)
1100 (display "22.22.0" port))))))))
1101 (native-inputs
1102 `(("cmake-minimal" ,cmake-minimal)
1103 ("clang" ,clang)
1104 ("pkg-config" ,pkg-config)
1105 ("bubblewrap-source" ,(package-source bubblewrap))))
1106 (inputs (cons* libcap openssl sqlite zlib `(,zstd "lib")
1107 (cargo-inputs 'codex-acp)))
1108 (home-page "https://github.com/zed-industries/codex-acp")
1109 (synopsis "ACP-compatible agent bridging Zed Codex with ACP clients")
1110 (description
1111 "This package provides an Agent Client Protocol (ACP) compatible agent
1112that bridges the Zed Codex runtime with ACP clients over stdio. It
1113supports multiple LLM providers through configuration in
1114@file{~/.codex/config.toml} and integrates with MCP servers for filesystem
1115operations.")
1116 (license license:asl2.0)))