From 87ce50f11870698249d57c387892c3a42222c2aa Mon Sep 17 00:00:00 2001
From: Javi <666807+JaviSoto@users.noreply.github.com>
Date: Fri, 6 Feb 2026 16:18:42 -0800
Subject: [PATCH] app-server: print help message to console when starting
websockets server (#10943)
Follow-up to https://github.com/openai/codex/pull/10693
---
codex-rs/Cargo.lock | 1 +
codex-rs/app-server/Cargo.toml | 1 +
codex-rs/app-server/src/transport.rs | 37 +++++++++++++++++++++++++++-
3 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock
index 2d217ea06..68d124eb4 100644
--- a/codex-rs/Cargo.lock
+++ b/codex-rs/Cargo.lock
@@ -1239,6 +1239,7 @@ dependencies = [
"core_test_support",
"futures",
"os_info",
+ "owo-colors",
"pretty_assertions",
"rmcp",
"serde",
diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml
index 2f6011a63..f68e07877 100644
--- a/codex-rs/app-server/Cargo.toml
+++ b/codex-rs/app-server/Cargo.toml
@@ -35,6 +35,7 @@ codex-utils-json-to-toml = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive"] }
futures = { workspace = true }
+owo-colors = { workspace = true, features = ["supports-colors"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tempfile = { workspace = true }
diff --git a/codex-rs/app-server/src/transport.rs b/codex-rs/app-server/src/transport.rs
index 52f30055d..39fd13212 100644
--- a/codex-rs/app-server/src/transport.rs
+++ b/codex-rs/app-server/src/transport.rs
@@ -5,6 +5,9 @@ use crate::outgoing_message::OutgoingMessage;
use codex_app_server_protocol::JSONRPCMessage;
use futures::SinkExt;
use futures::StreamExt;
+use owo_colors::OwoColorize;
+use owo_colors::Stream;
+use owo_colors::Style;
use std::collections::HashMap;
use std::io::ErrorKind;
use std::io::Result as IoResult;
@@ -33,6 +36,36 @@ use tracing::warn;
/// plenty for an interactive CLI.
pub(crate) const CHANNEL_CAPACITY: usize = 128;
+fn colorize(text: &str, style: Style) -> String {
+ text.if_supports_color(Stream::Stderr, |value| value.style(style))
+ .to_string()
+}
+
+#[allow(clippy::print_stderr)]
+fn print_websocket_startup_banner(addr: SocketAddr) {
+ let title = colorize("codex app-server (WebSockets)", Style::new().bold().cyan());
+ let listening_label = colorize("listening on:", Style::new().dimmed());
+ let listen_url = colorize(&format!("ws://{addr}"), Style::new().green());
+ let note_label = colorize("note:", Style::new().dimmed());
+ eprintln!("{title}");
+ eprintln!(" {listening_label} {listen_url}");
+ if addr.ip().is_loopback() {
+ eprintln!(
+ " {note_label} binds localhost only (use SSH port-forwarding for remote access)"
+ );
+ } else {
+ eprintln!(
+ " {note_label} this is a raw WS server; consider running behind TLS/auth for real remote use"
+ );
+ }
+}
+
+#[allow(clippy::print_stderr)]
+fn print_websocket_connection(peer_addr: SocketAddr) {
+ let connected_label = colorize("websocket client connected from", Style::new().dimmed());
+ eprintln!("{connected_label} {peer_addr}");
+}
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AppServerTransport {
Stdio,
@@ -191,13 +224,15 @@ pub(crate) async fn start_websocket_acceptor(
) -> IoResult> {
let listener = TcpListener::bind(bind_address).await?;
let local_addr = listener.local_addr()?;
+ print_websocket_startup_banner(local_addr);
info!("app-server websocket listening on ws://{local_addr}");
let connection_counter = Arc::new(AtomicU64::new(1));
Ok(tokio::spawn(async move {
loop {
match listener.accept().await {
- Ok((stream, _peer_addr)) => {
+ Ok((stream, peer_addr)) => {
+ print_websocket_connection(peer_addr);
let connection_id =
ConnectionId(connection_counter.fetch_add(1, Ordering::Relaxed));
let transport_event_tx_for_connection = transport_event_tx.clone();