Hello,

Please find attached a patch improving Codex compatibility with
OpenBSD while keeping the changes to upstream code minimal.

The main changes are:

- Sandbox: Reject unsupported sandbox modes instead of silently
  running commands without the requested restrictions. pledge/unveil
  cannot safely sandbox arbitrary subprocesses across execve.
  Unrestricted execution remains available when explicitly selected.

- Daemon: Keep the existing daemon functionality, but disable
  internal updates and downloads. Updates must go through pkg_add.
  Process identification is also made more reliable on OpenBSD.

- Shell: Support OpenBSD's native ksh and sh without requiring Bash.

- PTY: Improve file descriptor handling after fork to avoid
  unnecessary allocations and descriptor leaks.

- Clipboard: Enable the existing X11 clipboard implementation on
  OpenBSD without introducing another backend.

- Packaging: Declare missing runtime dependencies, fix PLIST
  ownership and document the supported features and limitations.

The purpose is to disable features that cannot provide their
expected guarantees safely, while enabling compatible features
through small changes rather than extensive platform-specific code.

I have been using Codex with these patches on OpenBSD 8.0-current
for intensive tasks over the past day without encountering any
problems.

The clipboard integration still needs to be tested.

I would appreciate review, particularly of the sandbox restrictions
and process execution changes.

Thank you.

Best regards,
David.
Index: devel/codex/Makefile
===================================================================
RCS file: /cvs/ports/devel/codex/Makefile,v
diff -u -p -u -p -r1.61 Makefile
--- devel/codex/Makefile	16 Oct 2026 09:32:32 -0000	1.61
+++ devel/codex/Makefile	18 Oct 2026 16:10:34 -0000
@@ -12,6 +12,7 @@ DPB_PROPERTIES =	parallel
 
 USE_WXNEEDED =		Yes
 
+REVISION = 		0
 V =			0.163.0
 GH_ACCOUNT =		openai
 GH_PROJECT =		codex
@@ -52,7 +53,9 @@ BUILD_DEPENDS =		devel/cmake/core \
 LIB_DEPENDS =		textproc/oniguruma \
 			x11/dbus
 
-RUN_DEPENDS =		lang/python/3 \
+RUN_DEPENDS =		devel/git \
+			devel/xdg-utils \
+			lang/python/3 \
 			textproc/ripgrep
 
 MODULES =		devel/cargo
Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs
diff -N devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,21 @@
+OpenBSD uses the generic ps-based process identity. Keep its lstart string
+independent of the invoking client's locale and timezone so start/stop from
+different terminals can verify the same daemon. Other platforms are unchanged.
+
+Index: codex-rs/app-server-daemon/src/backend/pid.rs
+--- codex-rs/app-server-daemon/src/backend/pid.rs.orig
++++ codex-rs/app-server-daemon/src/backend/pid.rs
+@@ -746,8 +746,11 @@ async fn read_process_start_time(pid: u3
+ 
+ #[cfg(unix)]
+ async fn read_process_details(pid: u32) -> Result<(String, String)> {
+-    let output = Command::new("ps")
+-        .args(["-p", &pid.to_string(), "-o", "stat=", "-o", "lstart="])
++    let mut command = Command::new("ps");
++    command.args(["-p", &pid.to_string(), "-o", "stat=", "-o", "lstart="]);
++    #[cfg(target_os = "openbsd")]
++    command.env("LC_ALL", "C").env("TZ", "UTC");
++    let output = command
+         .output()
+         .await
+         .context("failed to invoke ps for pid-managed app server")?;
Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_lib_rs_pkg_update
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_app-server-daemon_src_lib_rs_pkg_update
diff -N devel/codex/patches/patch-codex-rs_app-server-daemon_src_lib_rs_pkg_update
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_lib_rs_pkg_update	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,39 @@
+Reject internal daemon update entry points and keep the updater disabled.
+
+Index: codex-rs/app-server-daemon/src/lib.rs
+--- codex-rs/app-server-daemon/src/lib.rs.orig
++++ codex-rs/app-server-daemon/src/lib.rs
+@@ -282,6 +282,10 @@ pub async fn run_pid_update_loop(
+     restore_release: Option<String>,
+ ) -> Result<()> {
+     ensure_supported_platform()?;
++    anyhow::ensure!(
++        !cfg!(target_os = "openbsd"),
++        "the daemon is managed by the system package manager; use pkg_add -u"
++    );
+     #[cfg(windows)]
+     backend::windows::ensure_not_elevated()?;
+     update_loop::run(http_client_factory, restore_release).await
+@@ -291,6 +295,10 @@ pub async fn update(
+     http_client_factory: codex_http_client::HttpClientFactory,
+ ) -> Result<UpdateOutput> {
+     ensure_supported_platform()?;
++    anyhow::ensure!(
++        !cfg!(target_os = "openbsd"),
++        "the daemon is managed by the system package manager; use pkg_add -u"
++    );
+     #[cfg(windows)]
+     backend::windows::ensure_not_elevated()?;
+     update_loop::request_manual_update(&Daemon::from_environment()?, http_client_factory).await
+@@ -892,6 +900,11 @@ impl Daemon {
+ 
+     async fn ensure_managed_updater(&self, settings: &DaemonSettings) -> Result<bool> {
+         let updater = backend::pid_update_loop_backend(self.backend_paths(settings));
++        if cfg!(target_os = "openbsd") {
++            // Best effort: an invalid stale record must not prevent daemon start.
++            let _ = updater.stop().await;
++            return Ok(false);
++        }
+         if !settings.auto_update_enabled {
+             updater.stop().await?;
+             return Ok(false);
Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs_pkg_update
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs_pkg_update
diff -N devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs_pkg_update
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs_pkg_update	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,16 @@
+Reject --from-cli package replacement under the system package manager.
+
+Index: codex-rs/app-server-daemon/src/prepare_install.rs
+--- codex-rs/app-server-daemon/src/prepare_install.rs.orig
++++ codex-rs/app-server-daemon/src/prepare_install.rs
+@@ -52,6 +52,10 @@ pub async fn update_from_cli(
+     confirm: impl FnOnce(&InstallRequest) -> Result<bool>,
+ ) -> Result<Option<crate::UpdateOutput>> {
+     crate::ensure_supported_platform()?;
++    anyhow::ensure!(
++        !cfg!(target_os = "openbsd"),
++        "the daemon is managed by the system package manager; use pkg_add -u"
++    );
+     #[cfg(windows)]
+     crate::backend::windows::ensure_not_elevated()?;
+     let daemon = Daemon::from_environment()?;
Index: devel/codex/patches/patch-codex-rs_cli_src_main_rs_pkg_update
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_cli_src_main_rs_pkg_update
diff -N devel/codex/patches/patch-codex-rs_cli_src_main_rs_pkg_update
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_cli_src_main_rs_pkg_update	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,15 @@
+Reject the package-manager update path with an explicit pkg_add message.
+
+Index: codex-rs/cli/src/main.rs
+--- codex-rs/cli/src/main.rs.orig
++++ codex-rs/cli/src/main.rs
+@@ -874,6 +874,9 @@ fn resolve_windows_update_command_from_p
+ }
+ 
+ fn run_update_command() -> anyhow::Result<()> {
++    if cfg!(target_os = "openbsd") {
++        anyhow::bail!("Codex is managed by OpenBSD packages; update with pkg_add -u, then restart the daemon");
++    }
+     #[cfg(debug_assertions)]
+     {
+         anyhow::bail!(
Index: devel/codex/patches/patch-codex-rs_config_src_constraint_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_config_src_constraint_rs
diff -N devel/codex/patches/patch-codex-rs_config_src_constraint_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_config_src_constraint_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,14 @@
+Index: codex-rs/config/src/constraint.rs
+--- codex-rs/config/src/constraint.rs.orig
++++ codex-rs/config/src/constraint.rs
+@@ -6,6 +6,10 @@ use thiserror::Error;
+ 
+ #[derive(Debug, Error, PartialEq, Eq)]
+ pub enum ConstraintError {
++    #[cfg(target_os = "openbsd")]
++    #[error("OpenBSD cannot enforce managed sandbox permissions; explicitly select danger-full-access or externally supplied isolation")]
++    OpenBsdManagedSandboxUnavailable,
++
+     #[error(
+         "invalid value for `{field_name}`: `{candidate}` is not in the allowed set {allowed} (set by {requirement_source})"
+     )]
Index: devel/codex/patches/patch-codex-rs_core_src_config_mod_rs_sandbox
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_core_src_config_mod_rs_sandbox
diff -N devel/codex/patches/patch-codex-rs_core_src_config_mod_rs_sandbox
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_core_src_config_mod_rs_sandbox	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,19 @@
+Reject managed trusted-snapshot replacement on OpenBSD.
+
+Index: codex-rs/core/src/config/mod.rs
+--- codex-rs/core/src/config/mod.rs.orig
++++ codex-rs/core/src/config/mod.rs
+@@ -407,6 +407,13 @@ impl Permissions {
+         &mut self,
+         snapshot: PermissionProfileSnapshot,
+     ) -> ConstraintResult<()> {
++        #[cfg(target_os = "openbsd")]
++        if matches!(
++            snapshot.permission_profile(),
++            PermissionProfile::Managed { .. }
++        ) {
++            return Err(codex_config::ConstraintError::OpenBsdManagedSandboxUnavailable);
++        }
+         let permission_profile = Constrained::allow_only(snapshot.permission_profile().clone());
+         self.permission_profile_state =
+             PermissionProfileState::from_constrained_snapshot(permission_profile, snapshot)?;
Index: devel/codex/patches/patch-codex-rs_core_src_config_resolved_permission_profile_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_core_src_config_resolved_permission_profile_rs
diff -N devel/codex/patches/patch-codex-rs_core_src_config_resolved_permission_profile_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_core_src_config_resolved_permission_profile_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,45 @@
+Index: codex-rs/core/src/config/resolved_permission_profile.rs
+--- codex-rs/core/src/config/resolved_permission_profile.rs.orig
++++ codex-rs/core/src/config/resolved_permission_profile.rs
+@@ -11,6 +11,14 @@ pub(crate) struct PermissionProfileState
+     permission_profile: Constrained<PermissionProfileSnapshot>,
+ }
+ 
++#[cfg(target_os = "openbsd")]
++fn reject_openbsd_managed(permission_profile: &PermissionProfile) -> ConstraintResult<()> {
++    if matches!(permission_profile, PermissionProfile::Managed { .. }) {
++        return Err(codex_config::ConstraintError::OpenBsdManagedSandboxUnavailable);
++    }
++    Ok(())
++}
++
+ impl PermissionProfileState {
+     pub(crate) fn from_constrained_legacy(
+         constrained_permission_profile: Constrained<PermissionProfile>,
+@@ -72,6 +80,8 @@ impl PermissionProfileState {
+         &self,
+         permission_profile: &PermissionProfile,
+     ) -> ConstraintResult<()> {
++        #[cfg(target_os = "openbsd")]
++        reject_openbsd_managed(permission_profile)?;
+         let candidate = PermissionProfileSnapshot::legacy(permission_profile.clone());
+         self.permission_profile.can_set(&candidate)
+     }
+@@ -80,6 +90,8 @@ impl PermissionProfileState {
+         &mut self,
+         permission_profile: PermissionProfile,
+     ) -> ConstraintResult<()> {
++        #[cfg(target_os = "openbsd")]
++        reject_openbsd_managed(&permission_profile)?;
+         self.permission_profile
+             .set(PermissionProfileSnapshot::legacy(permission_profile))
+     }
+@@ -88,6 +100,8 @@ impl PermissionProfileState {
+         &mut self,
+         snapshot: PermissionProfileSnapshot,
+     ) -> ConstraintResult<()> {
++        #[cfg(target_os = "openbsd")]
++        reject_openbsd_managed(snapshot.permission_profile())?;
+         self.permission_profile.set(snapshot)
+     }
+ }
Index: devel/codex/patches/patch-codex-rs_core_src_session_mod_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_core_src_session_mod_rs
diff -N devel/codex/patches/patch-codex-rs_core_src_session_mod_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_core_src_session_mod_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,22 @@
+Reject managed sessions at the central session-spawn boundary. Configuration-only
+commands (login, doctor, MCP administration) and app-server startup remain usable.
+
+Index: codex-rs/core/src/session/mod.rs
+--- codex-rs/core/src/session/mod.rs.orig
++++ codex-rs/core/src/session/mod.rs
+@@ -593,6 +593,15 @@ impl Session {
+ 
+     async fn spawn_internal(args: SessionSpawnArgs) -> CodexResult<(Arc<Self>, SessionIo)> {
+         args.config.validate_windows_mxc_requirement()?;
++        #[cfg(target_os = "openbsd")]
++        if matches!(
++            args.config.permissions.permission_profile(),
++            PermissionProfile::Managed { .. }
++        ) {
++            return Err(CodexErr::UnsupportedOperation(
++                "OpenBSD cannot enforce a managed sandbox session; select explicit danger-full-access or external isolation".to_string(),
++            ));
++        }
+         let SessionSpawnArgs {
+             startup,
+             config,
Index: devel/codex/patches/patch-codex-rs_exec-server_src_process_sandbox_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_exec-server_src_process_sandbox_rs
diff -N devel/codex/patches/patch-codex-rs_exec-server_src_process_sandbox_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_exec-server_src_process_sandbox_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,18 @@
+Index: codex-rs/exec-server/src/process_sandbox.rs
+--- codex-rs/exec-server/src/process_sandbox.rs.orig
++++ codex-rs/exec-server/src/process_sandbox.rs
+@@ -95,6 +95,14 @@ pub(crate) async fn prepare_exec_request
+     network_policy_audit_observer: Option<NetworkPolicyAuditObserver>,
+     telemetry: &ProcessTelemetry,
+ ) -> Result<PreparedExecRequest, JSONRPCErrorError> {
++    // A proxy environment cannot enforce egress policy without a command
++    // sandbox. Check before the sandbox=None native-launch shortcut too.
++    #[cfg(target_os = "openbsd")]
++    if params.enforce_managed_network {
++        return Err(invalid_params(
++            "OpenBSD cannot enforce managed command networking: no supported command sandbox is available".to_string(),
++        ));
++    }
+     if let Some(sandbox) = params.sandbox.as_ref()
+         && sandbox.windows_sandbox_selection == WindowsSandboxSelection::Mxc
+     {
Index: devel/codex/patches/patch-codex-rs_sandboxing_src_lib_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_sandboxing_src_lib_rs
diff -N devel/codex/patches/patch-codex-rs_sandboxing_src_lib_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_sandboxing_src_lib_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,14 @@
+Index: codex-rs/sandboxing/src/lib.rs
+--- codex-rs/sandboxing/src/lib.rs.orig
++++ codex-rs/sandboxing/src/lib.rs
+@@ -75,6 +75,10 @@ pub fn system_bwrap_warning(
+ impl From<SandboxTransformError> for CodexErr {
+     fn from(err: SandboxTransformError) -> Self {
+         match err {
++            #[cfg(target_os = "openbsd")]
++            SandboxTransformError::OpenBsdSandboxUnavailable => CodexErr::UnsupportedOperation(
++                "OpenBSD has no supported command sandbox; select explicit danger-full-access or provide external isolation".to_string(),
++            ),
+             error @ SandboxTransformError::InvalidCommandCwd { .. }
+             | error @ SandboxTransformError::InvalidSandboxPolicyCwd { .. } => {
+                 CodexErr::InvalidRequest(error.to_string())
Index: devel/codex/patches/patch-codex-rs_sandboxing_src_manager_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_sandboxing_src_manager_rs
diff -N devel/codex/patches/patch-codex-rs_sandboxing_src_manager_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_sandboxing_src_manager_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,63 @@
+Index: codex-rs/sandboxing/src/manager.rs
+--- codex-rs/sandboxing/src/manager.rs.orig
++++ codex-rs/sandboxing/src/manager.rs
+@@ -189,6 +189,8 @@ impl PendingSandboxedExecRequest {
+ 
+ #[derive(Debug)]
+ pub enum SandboxTransformError {
++    #[cfg(target_os = "openbsd")]
++    OpenBsdSandboxUnavailable,
+     InvalidCommandCwd {
+         cwd: PathUri,
+         source: io::Error,
+@@ -213,6 +215,11 @@ pub enum SandboxTransformError {
+ impl std::fmt::Display for SandboxTransformError {
+     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+         match self {
++            #[cfg(target_os = "openbsd")]
++            Self::OpenBsdSandboxUnavailable => write!(
++                f,
++                "OpenBSD has no supported command sandbox; select explicit danger-full-access or provide external isolation"
++            ),
+             Self::InvalidCommandCwd { cwd, source } => {
+                 write!(
+                     f,
+@@ -256,6 +263,8 @@ impl std::error::Error for SandboxTransf
+             Self::MissingLinuxSandboxExecutable => None,
+             Self::WindowsMxcPreparation(_) => None,
+             Self::EnvironmentNetworkProxy(_) => None,
++            #[cfg(target_os = "openbsd")]
++            Self::OpenBsdSandboxUnavailable => None,
+             #[cfg(target_os = "macos")]
+             Self::SeatbeltPreparation(_) => None,
+             #[cfg(target_os = "linux")]
+@@ -375,6 +384,29 @@ impl SandboxManager {
+             network.and_then(NetworkProxy::managed_mitm_ca_trust_bundle_path);
+         let base_effective_permission_profile =
+             effective_permission_profile(permissions, additional_permissions.as_ref());
++        // OpenBSD has no upstream sandbox backend. Never interpret a missing
++        // backend as permission to run a restricted request without isolation.
++        #[cfg(target_os = "openbsd")]
++        {
++            let (file_system_policy, network_policy) =
++                base_effective_permission_profile.to_runtime_permissions();
++            if matches!(
++                &base_effective_permission_profile,
++                PermissionProfile::Managed { .. }
++            ) || sandbox != SandboxType::None
++                || (matches!(
++                    file_system_policy.kind,
++                    codex_protocol::permissions::FileSystemSandboxKind::Restricted
++                ) && !file_system_policy.has_full_disk_read_access())
++                || should_require_platform_sandbox(
++                    &file_system_policy,
++                    network_policy,
++                    enforce_managed_network,
++                )
++            {
++                return Err(SandboxTransformError::OpenBsdSandboxUnavailable);
++            }
++        }
+         let pending_sandboxed_request = PendingSandboxedExecRequest::new(
+             &command.cwd,
+             sandbox_policy_cwd,
Index: devel/codex/patches/patch-codex-rs_shell-command_src_shell_detect_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_shell-command_src_shell_detect_rs
diff -N devel/codex/patches/patch-codex-rs_shell-command_src_shell_detect_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_shell-command_src_shell_detect_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,42 @@
+Minimal OpenBSD ksh preference: recognize ksh and prefer ksh/sh fallback over bash/zsh.
+Index: codex-rs/shell-command/src/shell_detect.rs
+--- codex-rs/shell-command/src/shell_detect.rs.orig
++++ codex-rs/shell-command/src/shell_detect.rs
+@@ -41,6 +41,7 @@ pub fn detect_shell_type(shell_path: imp
+     match shell_path.as_os_str().to_str() {
+         Some("zsh") => Some(ShellType::Zsh),
+         Some("sh") => Some(ShellType::Sh),
++        Some("ksh") => Some(ShellType::Sh),
+         Some("cmd") => Some(ShellType::Cmd),
+         Some("bash") => Some(ShellType::Bash),
+         Some("pwsh") => Some(ShellType::PowerShell),
+@@ -250,9 +251,15 @@ fn get_bash_shell() -> Option<DetectedSh
+     })
+ }
+ 
++#[cfg(target_os = "openbsd")]
++const SH_FALLBACK_PATHS: &[&str] = &["/bin/ksh", "/bin/sh"];
++#[cfg(not(target_os = "openbsd"))]
+ const SH_FALLBACK_PATHS: &[&str] = &["/bin/sh"];
+ 
+ fn get_sh_shell() -> Option<DetectedShell> {
++    #[cfg(target_os = "openbsd")]
++    let shell_path = get_shell_path(ShellType::Sh, "ksh", SH_FALLBACK_PATHS);
++    #[cfg(not(target_os = "openbsd"))]
+     let shell_path = get_shell_path(ShellType::Sh, "sh", SH_FALLBACK_PATHS);
+ 
+     shell_path.map(|shell_path| DetectedShell {
+@@ -372,6 +379,13 @@ pub fn default_user_shell_from_path(user
+             user_default_shell
+                 .or_else(|| get_shell(ShellType::Zsh))
+                 .or_else(|| get_shell(ShellType::Bash))
++        } else if cfg!(target_os = "openbsd") {
++            // Prefer the platform ksh/sh over bash/zsh when the user shell is
++            // not a recognized explicit choice. An explicit bash/zsh stays.
++            user_default_shell
++                .or_else(|| get_shell(ShellType::Sh))
++                .or_else(|| get_shell(ShellType::Bash))
++                .or_else(|| get_shell(ShellType::Zsh))
+         } else {
+             user_default_shell
+                 .or_else(|| get_shell(ShellType::Bash))
Index: devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs
diff -N devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,28 @@
+Keep the daemon enabled while directing package updates to pkg_add.
+Do not offer self-installation actions for a distributor-owned executable.
+
+Index: codex-rs/tui/src/app/daemon_menu.rs
+--- codex-rs/tui/src/app/daemon_menu.rs.orig
++++ codex-rs/tui/src/app/daemon_menu.rs
+@@ -54,6 +54,10 @@ impl App {
+             Some(
+                 "Manage this server on its host. Local daemon updates are unavailable for remote connections.",
+             )
++        } else if cfg!(target_os = "openbsd") {
++            Some(
++                "The daemon is installed by the system package manager. Use pkg_add -u, then codex app-server daemon restart.",
++            )
+         } else if self.daemon_cli_executable.is_none() {
+             Some("Run the Codex CLI to manage the daemon from this menu.")
+         } else {
+@@ -160,7 +164,9 @@ impl App {
+         let Some(executable) = &self.daemon_cli_executable else {
+             return;
+         };
+-        if matches!(self.app_server_target, AppServerTarget::Remote { .. }) {
++        if matches!(self.app_server_target, AppServerTarget::Remote { .. })
++            || cfg!(target_os = "openbsd")
++        {
+             return;
+         }
+         let mut explanation = match source {
Index: devel/codex/patches/patch-codex-rs_tui_src_clipboard_copy_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_tui_src_clipboard_copy_rs
diff -N devel/codex/patches/patch-codex-rs_tui_src_clipboard_copy_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_tui_src_clipboard_copy_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,64 @@
+Keep the existing X11 clipboard lease alive on OpenBSD too. arboard uses
+its Unix/X11 backend here; no new clipboard implementation is introduced.
+
+Index: codex-rs/tui/src/clipboard_copy.rs
+--- codex-rs/tui/src/clipboard_copy.rs.orig
++++ codex-rs/tui/src/clipboard_copy.rs
+@@ -8,7 +8,7 @@
+ //! suppress native copying. Both the host and attached client's clipboards may
+ //! change. Outside tmux, Herdr, and SSH, use OSC 52 only if native copying fails.
+ //!
+-//! On Linux, X11 and some Wayland compositors require the process that wrote the
++//! On Linux and OpenBSD, X11 and some Wayland compositors require the process that wrote the
+ //! clipboard to keep its handle open. `ClipboardLease` wraps the `arboard::Clipboard`
+ //! so the copy worker can retain it for the lifetime of the TUI. On other platforms the lease
+ //! is always `None`.
+@@ -138,19 +138,19 @@ fn copy_to_clipboard(
+ 
+ /// Keeps a platform clipboard owner alive when the backend requires one.
+ ///
+-/// On Linux/X11 and some Wayland compositors, clipboard contents are served by the
++/// On X11 (including OpenBSD) and some Wayland compositors, clipboard contents are served by the
+ /// owning process. Dropping the `arboard::Clipboard` before the user pastes causes
+ /// the content to vanish. Store this lease on a session-lived owner so closing a
+-/// transient overlay does not release the clipboard contents. On non-Linux native paths and OSC 52
++/// transient overlay does not release the clipboard contents. On other native paths and OSC 52
+ /// paths the lease is `None` — those backends do not require process-lifetime
+ /// ownership.
+ pub(crate) struct ClipboardLease {
+-    #[cfg(target_os = "linux")]
++    #[cfg(any(target_os = "linux", target_os = "openbsd"))]
+     _clipboard: Option<arboard::Clipboard>,
+ }
+ 
+ impl ClipboardLease {
+-    #[cfg(target_os = "linux")]
++    #[cfg(any(target_os = "linux", target_os = "openbsd"))]
+     fn native_linux(clipboard: arboard::Clipboard) -> Self {
+         Self {
+             _clipboard: Some(clipboard),
+@@ -160,7 +160,7 @@ impl ClipboardLease {
+     #[cfg(test)]
+     pub(crate) fn test() -> Self {
+         Self {
+-            #[cfg(target_os = "linux")]
++            #[cfg(any(target_os = "linux", target_os = "openbsd"))]
+             _clipboard: None,
+         }
+     }
+@@ -279,12 +279,12 @@ fn arboard_copy(
+         None => clipboard.set_text(text),
+     }
+     .map_err(|e| format!("failed to set clipboard text: {e}"))?;
+-    // Linux clipboard owners must stay alive until the user pastes.
+-    #[cfg(target_os = "linux")]
++    // Linux and OpenBSD clipboard owners must stay alive until the user pastes.
++    #[cfg(any(target_os = "linux", target_os = "openbsd"))]
+     {
+         Ok(Some(ClipboardLease::native_linux(clipboard)))
+     }
+-    #[cfg(not(target_os = "linux"))]
++    #[cfg(not(any(target_os = "linux", target_os = "openbsd")))]
+     {
+         Ok(None)
+     }
Index: devel/codex/patches/patch-codex-rs_tui_src_update_action_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_tui_src_update_action_rs
diff -N devel/codex/patches/patch-codex-rs_tui_src_update_action_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_tui_src_update_action_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,13 @@
+Index: codex-rs/tui/src/update_action.rs
+--- codex-rs/tui/src/update_action.rs.orig
++++ codex-rs/tui/src/update_action.rs
+@@ -81,6 +81,9 @@ impl UpdateAction {
+ 
+ #[cfg(not(debug_assertions))]
+ pub fn get_update_action() -> Option<UpdateAction> {
++    if cfg!(target_os = "openbsd") {
++        return None; // Updates are managed by pkg_add, never an upstream installer.
++    }
+     UpdateAction::from_install_context(InstallContext::current())
+ }
+ 
Index: devel/codex/patches/patch-codex-rs_utils_pty_src_pty_rs
===================================================================
RCS file: devel/codex/patches/patch-codex-rs_utils_pty_src_pty_rs
diff -N devel/codex/patches/patch-codex-rs_utils_pty_src_pty_rs
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ devel/codex/patches/patch-codex-rs_utils_pty_src_pty_rs	18 Oct 2026 16:10:35 -0000
@@ -0,0 +1,62 @@
+Index: codex-rs/utils/pty/src/pty.rs
+--- codex-rs/utils/pty/src/pty.rs.orig
++++ codex-rs/utils/pty/src/pty.rs
+@@ -606,9 +606,57 @@ pub fn close_inherited_fds_except(preser
+     }
+ }
+ 
++// OpenBSD has no allocating descriptor enumeration in this post-fork path.
++// Count valid descriptors rather than using resource limits: inherited high
++// descriptors may remain open after the process lowers its limits.
++#[cfg(target_os = "openbsd")]
++pub(crate) fn close_inherited_fds_except(preserved_fds: &[RawFd]) {
++    unsafe extern "C" {
++        // OpenBSD syscall; not exposed by all supported libc crate versions.
++        fn getdtablecount() -> libc::c_int;
++    }
++
++    // After fork this child has its own descriptor table and one thread. Block
++    // signal handlers while counting/scanning so they cannot change that table.
++    // Cleanup remains best-effort, matching the other platform implementations.
++    unsafe {
++        let mut all_signals: libc::sigset_t = std::mem::zeroed();
++        let mut old_mask: libc::sigset_t = std::mem::zeroed();
++        if libc::sigfillset(&mut all_signals) == -1
++            || libc::sigprocmask(libc::SIG_BLOCK, &all_signals, &mut old_mask) == -1
++        {
++            return;
++        }
++        let mut remaining = getdtablecount();
++        for fd in 0..=RawFd::MAX {
++            if remaining <= 0 {
++                break;
++            }
++            let flags = libc::fcntl(fd, libc::F_GETFD);
++            if flags == -1 {
++                continue;
++            }
++            // Count every valid descriptor, including preserved and CLOEXEC
++            // descriptors, even though those must remain open until exec.
++            remaining -= 1;
++            if fd > libc::STDERR_FILENO
++                && !preserved_fds.contains(&fd)
++                && flags & libc::FD_CLOEXEC == 0
++            {
++                // Keep std::process's CLOEXEC exec-error pipe intact.
++                libc::close(fd);
++            }
++        }
++        libc::sigprocmask(libc::SIG_SETMASK, &old_mask, std::ptr::null_mut());
++    }
++}
++
+ // Other Unix platforms use best-effort /dev/fd cleanup.
+ // Directory enumeration allocates, so this path is not guaranteed fork-safe.
+-#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
++#[cfg(all(
++    unix,
++    not(any(target_os = "linux", target_os = "macos", target_os = "openbsd"))
++))]
+ pub(crate) fn close_inherited_fds_except(preserved_fds: &[RawFd]) {
+     if let Ok(dir) = std::fs::read_dir("/dev/fd") {
+         let mut fds = Vec::new();
Index: devel/codex/pkg/PLIST
===================================================================
RCS file: /cvs/ports/devel/codex/pkg/PLIST,v
diff -u -p -u -p -r1.8 PLIST
--- devel/codex/pkg/PLIST	9 Aug 2026 11:29:25 -0000	1.8
+++ devel/codex/pkg/PLIST	18 Oct 2026 16:10:35 -0000
@@ -19,7 +19,5 @@ share/doc/codex/sandbox.md
 share/doc/codex/skills.md
 share/doc/codex/slash_commands.md
 share/doc/pkg-readmes/${PKGSTEM}
-share/fish/
-share/fish/vendor_completions.d/
 share/fish/vendor_completions.d/codex.fish
 share/zsh/site-functions/_codex

Reply via email to