Index | Thread | Search

From:
David Uhden Collado <daviduhden@gmail.com>
Subject:
Re: [IMPROVEMENT] Integrate the app server daemon into devel/codex.
To:
ports@openbsd.org, kirill@korins.ky, stu@spacehopper.org
Date:
Wed, 14 Oct 2026 19:56:00 +0000

Download raw body.

Thread
Kirill A. Korinsky wrote:
> On Tue, 13 Oct 2026 21:59:00 +0200,
> David Uhden Collado <daviduhden@gmail.com> wrote:
>>
>> You're right. The previous diff included more than the daemon
>> integration, and I should have made that clearer. Sorry for the
>> confusion.
>>
> 
> You did it again.
> 
>> I've attached a revised diff against the current CVS tree, including the
>> previously missing patch files.
>>
>> The daemon integration itself is relatively small. It reuses the
>> existing Codex executable as the app-server daemon, installs it with the
>> required package manifest under libexec/codex, and makes the daemon
>> resolver use that executable instead of downloading or copying another
>> one into ~/.codex/packages.
>>
>> The existing upstream process management, Unix sockets, IPC, and
>> start/stop/restart functionality remain unchanged. Only internal
>> installation and self-update mechanisms are disabled, since pkg_add owns
>> the executable.
> 
> Why do we need that daemon on the first place?
> 
> I not sure that this is sane wrokflow:
> 
>> +	codex app-server daemon start
>> +	codex app-server daemon version
>> +	codex app-server daemon restart
>> +	codex app-server daemon stop
> 
> What a use case do you have for that daemon?
> 
>> - Enable OpenBSD support in the WASM interpreter platform checks.
>> - Use MAP_STACK for the usable WASM stack region while preserving its
>> guard pages.
> 
> Does codex use wasm? really?
> 
I've attached a minimal patch to this email containing only the changes 
needed to enable the Codex daemon, without the other V8-related changes, 
additional comments, or indentation changes.
Index: devel/codex/Makefile
===================================================================
--- devel/codex/Makefile
+++ devel/codex/Makefile
@@ -17,6 +17,7 @@
 GH_PROJECT =		codex
 GH_TAGNAME =		rust-v${V}
 PKGNAME =		codex-${V}
+REVISION =		0
 
 # only build the Rust workspace (the repo also contains JS tooling).
 WRKSRC =		${WRKDIST}/codex-rs
@@ -95,6 +96,12 @@
 			--bin codex \
 			--bin codex-code-mode-host
 
+# Use the package-owned executable for the per-user daemon.
+MODCARGO_ENV +=		CODEX_SYSTEM_DAEMON_PATH=${LOCALBASE}/libexec/codex/bin/codex
+
+CODEX_TARGET =		${MACHINE_ARCH:S/amd64/x86_64/}-unknown-openbsd
+SUBST_VARS +=		V CODEX_TARGET
+
 MAKE_ENV =		${MODCARGO_ENV}
 
 # test suite expects network access and a configured OpenAI account
@@ -111,12 +118,22 @@
 	    ${V8_DISTDIR}/third_party/icu/common/
 
 post-build:
+	${SUBST_CMD} -m 644 -c ${FILESDIR}/codex-package.json \
+		${WRKBUILD}/codex-package.json
 .for s in bash fish zsh
 	cd ${WRKBUILD}/target/release && \
 		./codex completion $s > codex.$s
 .endfor
 
 post-install:
+	${INSTALL_DATA_DIR} ${PREFIX}/libexec/codex/bin
+	mv ${PREFIX}/bin/codex ${PREFIX}/libexec/codex/bin/
+	mv ${PREFIX}/bin/codex-code-mode-host ${PREFIX}/libexec/codex/bin/
+	ln -s ../libexec/codex/bin/codex ${PREFIX}/bin/codex
+	ln -s ../libexec/codex/bin/codex-code-mode-host \
+		${PREFIX}/bin/codex-code-mode-host
+	${INSTALL_DATA} ${WRKBUILD}/codex-package.json \
+		${PREFIX}/libexec/codex/
 	mv ${PREFIX}/bin/logs_client ${PREFIX}/bin/codex-logs-client
 	${INSTALL_DATA_DIR} ${PREFIX}/share/bash-completion/completions \
 		${PREFIX}/share/fish/vendor_completions.d \
Index: devel/codex/files/codex-package.json
===================================================================
--- /dev/null
+++ devel/codex/files/codex-package.json
@@ -0,0 +1,7 @@
+{
+  "layoutVersion": 1,
+  "version": "${V}",
+  "target": "${CODEX_TARGET}",
+  "variant": "codex",
+  "entrypoint": "bin/codex"
+}
Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs
===================================================================
--- /dev/null
+++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_backend_pid_rs
@@ -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: u32) -> Result<S
+ 
+ #[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
===================================================================
--- /dev/null
+++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_lib_rs
@@ -0,0 +1,91 @@
+Use a distributor-owned executable with the normal daemon lifecycle and IPC.
+Reject binary self-updates and incompatible running daemons; installation
+and replacement belong to pkg_add.
+
+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
+@@ -225,6 +225,12 @@ enum RestartDecision {
+     Restart,
+ }
+ 
++/// A distributor-owned daemon is installed and updated outside Codex.
++/// Process lifecycle and IPC still use the normal managed-daemon backend.
++pub fn system_daemon_path() -> Option<&'static Path> {
++    managed_install::system_codex_bin()
++}
++
+ pub async fn run(command: LifecycleCommand) -> Result<LifecycleOutput> {
+     ensure_supported_platform()?;
+     #[cfg(windows)]
+@@ -282,6 +288,10 @@ pub async fn run_pid_update_loop(
+     restore_release: Option<String>,
+ ) -> Result<()> {
+     ensure_supported_platform()?;
++    anyhow::ensure!(
++        managed_install::system_codex_bin().is_none(),
++        "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 +301,20 @@ pub async fn update(
+     http_client_factory: codex_http_client::HttpClientFactory,
+ ) -> Result<UpdateOutput> {
+     ensure_supported_platform()?;
++    if let Some(path) = managed_install::system_codex_bin() {
++        let daemon = Daemon::from_environment()?;
++        return Ok(UpdateOutput {
++            status: UpdateStatus::Unsupported,
++            managed_codex_path: path.to_path_buf(),
++            installed_version: daemon.managed_codex_version_best_effort().await,
++            running_version: client::probe(&daemon.socket_path)
++                .await
++                .ok()
++                .map(|info| info.app_server_version),
++            message: "The daemon is managed by the system package manager; use pkg_add -u."
++                .to_string(),
++        });
++    }
+     #[cfg(windows)]
+     backend::windows::ensure_not_elevated()?;
+     update_loop::request_manual_update(&Daemon::from_environment()?, http_client_factory).await
+@@ -448,6 +472,15 @@ impl Daemon {
+                 self.wait_until_ready().await?,
+             )
+         };
++        if managed_install::system_codex_bin().is_some() {
++            anyhow::ensure!(
++                info.app_server_version == env!("CARGO_PKG_VERSION"),
++                "system-managed daemon is running version {} but this CLI requires {}; \
++                 run `codex app-server daemon restart` after pkg_add -u",
++                info.app_server_version,
++                env!("CARGO_PKG_VERSION")
++            );
++        }
+         if backend.is_some()
+             && let Err(err) = managed.ensure_managed_updater(&settings).await
+         {
+@@ -892,6 +925,10 @@ impl Daemon {
+ 
+     async fn ensure_managed_updater(&self, settings: &DaemonSettings) -> Result<bool> {
+         let updater = backend::pid_update_loop_backend(self.backend_paths(settings));
++        if managed_install::system_codex_bin().is_some() {
++            updater.stop().await?;
++            return Ok(false);
++        }
+         if !settings.auto_update_enabled {
+             updater.stop().await?;
+             return Ok(false);
+@@ -981,6 +1018,11 @@ impl Daemon {
+         }
+ 
+         let managed_codex_path = self.managed_codex_bin.display();
++        if managed_install::system_codex_bin().is_some() {
++            return Err(anyhow!(
++                "system daemon executable not found at {managed_codex_path}; reinstall the codex package with pkg_add"
++            ));
++        }
+         Err(anyhow!(
+             "daemon executable not found at {managed_codex_path}; repair the existing installation, or run `codex app-server daemon start` to install a missing daemon"
+         ))
Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_managed_install_rs
===================================================================
--- /dev/null
+++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_managed_install_rs
@@ -0,0 +1,42 @@
+Allow the port to select its already installed codex as the app-server
+executable. System ownership is a build-time setting and takes precedence
+over user-managed packages; it never opts into the internal release updater.
+
+Index: codex-rs/app-server-daemon/src/managed_install.rs
+--- codex-rs/app-server-daemon/src/managed_install.rs.orig
++++ codex-rs/app-server-daemon/src/managed_install.rs
+@@ -14,6 +14,14 @@ use tokio::fs;
+ use tokio::process::Command;
+ use tokio::time::timeout;
+ 
++/// A distributor-owned executable has no managed package or internal updater.
++/// This is a build-time choice, so the runtime environment cannot redirect it.
++pub(crate) fn system_codex_bin() -> Option<&'static Path> {
++    option_env!("CODEX_SYSTEM_DAEMON_PATH")
++        .filter(|path| !path.is_empty())
++        .map(Path::new)
++}
++
+ /// New daemons own their packages, regardless of how the calling CLI was installed.
+ /// Preserve legacy launch state, including logs left after a daemon is stopped;
+ /// settings, installer selections, and lock files alone do not prove a prior launch.
+@@ -57,6 +65,9 @@ pub(crate) fn package_root(codex_home: &Path) -> PathB
+ 
+ /// Resolve both packaged and legacy binaries without requiring a valid install.
+ pub(crate) fn managed_codex_bin(codex_home: &Path) -> PathBuf {
++    if let Some(path) = system_codex_bin() {
++        return path.to_path_buf();
++    }
+     let root = package_root(codex_home);
+     let current = root.join("current");
+     let packaged = current.join("bin").join(managed_codex_file_name());
+@@ -72,6 +83,9 @@ pub(crate) fn managed_codex_bin(codex_home: &Path) -> 
+ 
+ /// Only latest-channel stable releases may run the public latest-version updater.
+ pub(crate) fn is_stable_standalone_release(codex_home: &Path, codex_bin: &Path) -> bool {
++    if system_codex_bin().is_some() {
++        return false;
++    }
+     let standalone = package_root(codex_home);
+     let Ok(releases) = std::fs::canonicalize(standalone.join("releases")) else {
+         return false;
Index: devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs
===================================================================
--- /dev/null
+++ devel/codex/patches/patch-codex-rs_app-server-daemon_src_prepare_install_rs
@@ -0,0 +1,56 @@
+Validate the system package manifest and executable version without copying
+a CLI package into CODEX_HOME. Reject explicit --from-cli replacement of
+an executable owned by 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
+@@ -26,6 +26,37 @@ pub struct InstallRequest {
+ 
+ /// Prepare a missing package while the caller holds the daemon operation lock.
+ pub(super) async fn prepare(daemon: &Daemon, settings: &DaemonSettings) -> Result<()> {
++    if let Some(path) = managed_install::system_codex_bin() {
++        daemon.ensure_managed_codex_bin()?;
++        let context = InstallContext::from_exe(
++            cfg!(target_os = "macos"),
++            Some(path),
++            /*method_override*/ None,
++        );
++        let manifest = context.package_manifest().with_context(|| {
++            format!(
++                "system Codex package metadata missing or invalid for {}; reinstall the codex package with pkg_add",
++                path.display()
++            )
++        })?;
++        let expected = env!("CARGO_PKG_VERSION");
++        anyhow::ensure!(
++            manifest.version.to_string() == expected,
++            "system Codex package version {} differs from CLI version {expected}; update the codex package with pkg_add -u",
++            manifest.version
++        );
++        let version = tokio::time::timeout(
++            std::time::Duration::from_secs(5),
++            managed_install::managed_codex_version(path),
++        )
++        .await
++        .context("timed out checking the system Codex executable version")??;
++        anyhow::ensure!(
++            version == expected,
++            "system Codex executable version {version} differs from CLI version {expected}; update the codex package with pkg_add -u"
++        );
++        return Ok(());
++    }
+     let source = InstallContext::current().package_layout.as_ref();
+     // Keep package replacement state out of the CLI dispatcher's async stack frame.
+     Box::pin(prepare_from_package(
+@@ -46,6 +77,10 @@ pub async fn update_from_cli(
+     confirm: impl FnOnce(&InstallRequest) -> Result<bool>,
+ ) -> Result<Option<crate::UpdateOutput>> {
+     crate::ensure_supported_platform()?;
++    anyhow::ensure!(
++        managed_install::system_codex_bin().is_none(),
++        "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_features_src_lib_rs
===================================================================
--- devel/codex/patches/patch-codex-rs_features_src_lib_rs
+++ /dev/null
@@ -1,15 +0,0 @@
-Disable daemon auto start by default
-https://github.com/openai/codex/issues/48050
-
-Index: codex-rs/features/src/lib.rs
---- codex-rs/features/src/lib.rs.orig
-+++ codex-rs/features/src/lib.rs
-@@ -992,7 +992,7 @@ pub const FEATURES: &[FeatureSpec] = &[
-         id: Feature::DaemonAutoStart,
-         key: "daemon_auto_start",
-         stage: Stage::Stable,
--        default_enabled: true,
-+        default_enabled: false,
-     },
-     FeatureSpec {
-         id: Feature::TranscriptV2,
Index: devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs
===================================================================
--- /dev/null
+++ devel/codex/patches/patch-codex-rs_tui_src_app_daemon_menu_rs
@@ -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
+@@ -39,6 +39,10 @@ impl App {
+             Some(
+                 "Manage this server on its host. Local daemon updates are unavailable for remote connections.",
+             )
++        } else if codex_app_server_daemon::system_daemon_path().is_some() {
++            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 {
+@@ -89,7 +93,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 { .. })
++            || codex_app_server_daemon::system_daemon_path().is_some()
++        {
+             return;
+         }
+         let mut explanation = match source {
Index: devel/codex/pkg/PLIST
===================================================================
--- devel/codex/pkg/PLIST
+++ devel/codex/pkg/PLIST
@@ -1,6 +1,11 @@
-@bin bin/codex
-@bin bin/codex-code-mode-host
+bin/codex
+bin/codex-code-mode-host
 @bin bin/codex-logs-client
+libexec/codex/
+libexec/codex/bin/
+@bin libexec/codex/bin/codex
+@bin libexec/codex/bin/codex-code-mode-host
+libexec/codex/codex-package.json
 share/bash-completion/completions/codex
 share/doc/codex/
 share/doc/codex/CLA.md
Index: devel/codex/pkg/README
===================================================================
--- devel/codex/pkg/README
+++ devel/codex/pkg/README
@@ -20,3 +20,16 @@
 
 Also, ${PKGSTEM} may use a lot of open files, you may need to raise both
 the system-wide maxfiles and used login class limits.
+
+The app-server daemon runs on demand as the invoking user, using the
+package-owned executable in ${LOCALBASE}/libexec/codex/bin. There is no
+system rc.d service. Manage it with:
+
+	codex app-server daemon start
+	codex app-server daemon version
+	codex app-server daemon restart
+	codex app-server daemon stop
+
+Use pkg_add -u to update the package, then restart the daemon. Internal
+binary updates and --from-cli replacement are disabled. State remains in
+CODEX_HOME (default ~/.codex); no daemon package is copied there.