Add config to disable /feedback (#8909)

Some enterprises do not want their users to be able to `/feedback`.

<img width="395" height="325" alt="image"
src="https://github.com/user-attachments/assets/2dae9c0b-20c3-4a15-bcd3-0187857ebbd8"
/>

Adds to `config.toml`:

```toml
[feedback]
enabled = false
```

I've deliberately decided to:
1. leave other references to `/feedback` (e.g. in the interrupt message,
tips of the day) unchanged. I think we should continue to promote the
feature even if it is not usable currently.
2. leave the `/feedback` menu item selectable and display an error
saying it's disabled, rather than remove the menu item (which I believe
would raise more questions).

but happy to discuss these.

This will be followed by a change to requirements.toml that admins can
use to force the value of feedback.enabled.
This commit is contained in:
gt-oai
2026-01-09 16:33:48 +00:00
committed by GitHub
Unverified
parent ea56186c2b
commit 5b5a5b92b5
9 changed files with 97 additions and 0 deletions
@@ -3678,6 +3678,16 @@ impl CodexMessageProcessor {
}
async fn upload_feedback(&self, request_id: RequestId, params: FeedbackUploadParams) {
if !self.config.feedback_enabled {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: "sending feedback is disabled by configuration".to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
let FeedbackUploadParams {
classification,
reason,
+37
View File
@@ -356,6 +356,10 @@ pub struct Config {
/// Defaults to `true`.
pub analytics: bool,
/// When `false`, disables feedback collection across Codex product surfaces.
/// Defaults to `true`.
pub feedback_enabled: bool,
/// OTEL configuration (exporter type, endpoint, headers, etc.).
pub otel: crate::config::types::OtelConfig,
}
@@ -820,6 +824,10 @@ pub struct ConfigToml {
/// Defaults to `true`.
pub analytics: Option<crate::config::types::AnalyticsConfigToml>,
/// When `false`, disables feedback collection across Codex product surfaces.
/// Defaults to `true`.
pub feedback: Option<crate::config::types::FeedbackConfigToml>,
/// OTEL configuration.
pub otel: Option<crate::config::types::OtelConfigToml>,
@@ -1403,6 +1411,11 @@ impl Config {
.and_then(|a| a.enabled)
.or(cfg.analytics.as_ref().and_then(|a| a.enabled))
.unwrap_or(true),
feedback_enabled: cfg
.feedback
.as_ref()
.and_then(|feedback| feedback.enabled)
.unwrap_or(true),
tui_notifications: cfg
.tui
.as_ref()
@@ -1559,6 +1572,7 @@ mod tests {
use crate::config::edit::ConfigEdit;
use crate::config::edit::ConfigEditsBuilder;
use crate::config::edit::apply_blocking;
use crate::config::types::FeedbackConfigToml;
use crate::config::types::HistoryPersistence;
use crate::config::types::McpServerTransportConfig;
use crate::config::types::Notifications;
@@ -1885,6 +1899,25 @@ trust_level = "trusted"
Ok(())
}
#[test]
fn feedback_enabled_defaults_to_true() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let cfg = ConfigToml {
feedback: Some(FeedbackConfigToml::default()),
..Default::default()
};
let config = Config::load_from_base_config_with_overrides(
cfg,
ConfigOverrides::default(),
codex_home.path().to_path_buf(),
)?;
assert_eq!(config.feedback_enabled, true);
Ok(())
}
#[test]
fn profile_legacy_toggles_override_base() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
@@ -3234,6 +3267,7 @@ model_verbosity = "high"
animations: true,
show_tooltips: true,
analytics: true,
feedback_enabled: true,
tui_scroll_events_per_tick: None,
tui_scroll_wheel_lines: None,
tui_scroll_trackpad_lines: None,
@@ -3318,6 +3352,7 @@ model_verbosity = "high"
animations: true,
show_tooltips: true,
analytics: true,
feedback_enabled: true,
tui_scroll_events_per_tick: None,
tui_scroll_wheel_lines: None,
tui_scroll_trackpad_lines: None,
@@ -3417,6 +3452,7 @@ model_verbosity = "high"
animations: true,
show_tooltips: true,
analytics: false,
feedback_enabled: true,
tui_scroll_events_per_tick: None,
tui_scroll_wheel_lines: None,
tui_scroll_trackpad_lines: None,
@@ -3502,6 +3538,7 @@ model_verbosity = "high"
animations: true,
show_tooltips: true,
analytics: true,
feedback_enabled: true,
tui_scroll_events_per_tick: None,
tui_scroll_wheel_lines: None,
tui_scroll_trackpad_lines: None,
+6
View File
@@ -282,6 +282,12 @@ pub struct AnalyticsConfigToml {
pub enabled: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
pub struct FeedbackConfigToml {
/// When `false`, disables the feedback flow across Codex product surfaces.
pub enabled: Option<bool>,
}
// ===== OTEL configuration =====
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
@@ -380,6 +380,21 @@ pub(crate) fn feedback_selection_params(
}
}
/// Build the selection popup params shown when feedback is disabled.
pub(crate) fn feedback_disabled_params() -> super::SelectionViewParams {
super::SelectionViewParams {
title: Some("Sending feedback is disabled".to_string()),
subtitle: Some("This action is disabled by configuration.".to_string()),
footer_hint: Some(standard_popup_hint_line()),
items: vec![super::SelectionItem {
name: "Close".to_string(),
dismiss_on_select: true,
..Default::default()
}],
..Default::default()
}
}
fn make_feedback_item(
app_event_tx: AppEventSender,
name: &str,
+1
View File
@@ -34,6 +34,7 @@ mod prompt_args;
mod skill_popup;
pub(crate) use list_selection_view::SelectionViewParams;
mod feedback_view;
pub(crate) use feedback_view::feedback_disabled_params;
pub(crate) use feedback_view::feedback_selection_params;
pub(crate) use feedback_view::feedback_upload_consent_params;
mod paste_burst;
+6
View File
@@ -1703,6 +1703,12 @@ impl ChatWidget {
}
match cmd {
SlashCommand::Feedback => {
if !self.config.feedback_enabled {
let params = crate::bottom_pane::feedback_disabled_params();
self.bottom_pane.show_selection_view(params);
self.request_redraw();
return;
}
// Step 1: pick a category (UI built in feedback_view)
let params =
crate::bottom_pane::feedback_selection_params(self.app_event_tx.clone());
@@ -380,6 +380,21 @@ pub(crate) fn feedback_selection_params(
}
}
/// Build the selection popup params shown when feedback is disabled.
pub(crate) fn feedback_disabled_params() -> super::SelectionViewParams {
super::SelectionViewParams {
title: Some("Sending feedback is disabled".to_string()),
subtitle: Some("This action is disabled by configuration.".to_string()),
footer_hint: Some(standard_popup_hint_line()),
items: vec![super::SelectionItem {
name: "Close".to_string(),
dismiss_on_select: true,
..Default::default()
}],
..Default::default()
}
}
fn make_feedback_item(
app_event_tx: AppEventSender,
name: &str,
+1
View File
@@ -32,6 +32,7 @@ mod prompt_args;
mod skill_popup;
pub(crate) use list_selection_view::SelectionViewParams;
mod feedback_view;
pub(crate) use feedback_view::feedback_disabled_params;
pub(crate) use feedback_view::feedback_selection_params;
pub(crate) use feedback_view::feedback_upload_consent_params;
mod paste_burst;
+6
View File
@@ -1537,6 +1537,12 @@ impl ChatWidget {
}
match cmd {
SlashCommand::Feedback => {
if !self.config.feedback_enabled {
let params = crate::bottom_pane::feedback_disabled_params();
self.bottom_pane.show_selection_view(params);
self.request_redraw();
return;
}
// Step 1: pick a category (UI built in feedback_view)
let params =
crate::bottom_pane::feedback_selection_params(self.app_event_tx.clone());