Files
codex/codex-rs/debug-client/src/output.rs
T
Michael Bolin 9a8730f31e ci: verify codex-rs Cargo manifests inherit workspace settings (#16353)
## Why

Bazel clippy now catches lints that `cargo clippy` can still miss when a
crate under `codex-rs` forgets to opt into workspace lints. The concrete
example here was `codex-rs/app-server/tests/common/Cargo.toml`: Bazel
flagged a clippy violation in `models_cache.rs`, but Cargo did not
because that crate inherited workspace package metadata without
declaring `[lints] workspace = true`.

We already mirror the workspace clippy deny list into Bazel after
[#15955](https://github.com/openai/codex/pull/15955), so we also need a
repo-side check that keeps every `codex-rs` manifest opted into the same
workspace settings.

## What changed

- add `.github/scripts/verify_cargo_workspace_manifests.py`, which
parses every `codex-rs/**/Cargo.toml` with `tomllib` and verifies:
  - `version.workspace = true`
  - `edition.workspace = true`
  - `license.workspace = true`
  - `[lints] workspace = true`
- top-level crate names follow the `codex-*` / `codex-utils-*`
conventions, with explicit exceptions for `windows-sandbox-rs` and
`utils/path-utils`
- run that script in `.github/workflows/ci.yml`
- update the current outlier manifests so the check is enforceable
immediately
- fix the newly exposed clippy violations in the affected crates
(`app-server/tests/common`, `file-search`, `feedback`,
`shell-escalation`, and `debug-client`)






---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/16353).
* #16351
* __->__ #16353
2026-03-31 21:59:28 +00:00

123 lines
3.3 KiB
Rust

#![allow(clippy::expect_used)]
use std::io;
use std::io::IsTerminal;
use std::io::Write;
use std::sync::Arc;
use std::sync::Mutex;
#[derive(Clone, Copy, Debug)]
pub enum LabelColor {
Assistant,
Tool,
ToolMeta,
Thread,
}
#[derive(Debug, Default)]
struct PromptState {
thread_id: Option<String>,
visible: bool,
}
#[derive(Clone, Debug)]
pub struct Output {
lock: Arc<Mutex<()>>,
prompt: Arc<Mutex<PromptState>>,
color: bool,
}
impl Output {
pub fn new() -> Self {
let no_color = std::env::var_os("NO_COLOR").is_some();
let color = !no_color && io::stdout().is_terminal() && io::stderr().is_terminal();
Self {
lock: Arc::new(Mutex::new(())),
prompt: Arc::new(Mutex::new(PromptState::default())),
color,
}
}
pub fn server_line(&self, line: &str) -> io::Result<()> {
let _guard = self.lock.lock().expect("output lock poisoned");
self.clear_prompt_line_locked()?;
let mut stdout = io::stdout();
writeln!(stdout, "{line}")?;
stdout.flush()?;
self.redraw_prompt_locked()
}
pub fn client_line(&self, line: &str) -> io::Result<()> {
let _guard = self.lock.lock().expect("output lock poisoned");
self.clear_prompt_line_locked()?;
let mut stderr = io::stderr();
writeln!(stderr, "{line}")?;
stderr.flush()
}
pub fn prompt(&self, thread_id: &str) -> io::Result<()> {
let _guard = self.lock.lock().expect("output lock poisoned");
self.set_prompt_locked(thread_id);
self.write_prompt_locked()
}
pub fn set_prompt(&self, thread_id: &str) {
let _guard = self.lock.lock().expect("output lock poisoned");
self.set_prompt_locked(thread_id);
}
pub fn format_label(&self, label: &str, color: LabelColor) -> String {
if !self.color {
return label.to_string();
}
let code = match color {
LabelColor::Assistant => "32",
LabelColor::Tool => "36",
LabelColor::ToolMeta => "33",
LabelColor::Thread => "34",
};
format!("\x1b[{code}m{label}\x1b[0m")
}
fn clear_prompt_line_locked(&self) -> io::Result<()> {
let mut prompt = self.prompt.lock().expect("prompt lock poisoned");
if prompt.visible {
let mut stderr = io::stderr();
writeln!(stderr)?;
stderr.flush()?;
prompt.visible = false;
}
Ok(())
}
fn redraw_prompt_locked(&self) -> io::Result<()> {
if self
.prompt
.lock()
.expect("prompt lock poisoned")
.thread_id
.is_some()
{
self.write_prompt_locked()?;
}
Ok(())
}
fn set_prompt_locked(&self, thread_id: &str) {
let mut prompt = self.prompt.lock().expect("prompt lock poisoned");
prompt.thread_id = Some(thread_id.to_string());
}
fn write_prompt_locked(&self) -> io::Result<()> {
let mut prompt = self.prompt.lock().expect("prompt lock poisoned");
let Some(thread_id) = prompt.thread_id.as_ref() else {
return Ok(());
};
let mut stderr = io::stderr();
write!(stderr, "({thread_id})> ")?;
stderr.flush()?;
prompt.visible = true;
Ok(())
}
}