deprecate legacy notify (#20524)

# Why

`notify` is the remaining compatibility surface from the legacy hook
implementation. The newer lifecycle hook engine now owns the active hook
system, so we should start steering users away from adding new `notify`
configs before removing the old path entirely. This also adds a
lightweight watchpoint for the deprecation so we can see how much legacy
usage remains before the clean drop.

# What

- emit a startup deprecation notice when a non-empty `notify` command is
configured
- emit `codex.notify.configured` when a session starts with legacy
`notify` configured
- emit `codex.notify.run` when the legacy notify path fires after a
completed turn
- mark `notify` as deprecated in the config schema and repo docs
- remove the orphaned `codex-rs/hooks/src/user_notification.rs` file
that is no longer compiled
- add regression coverage for the new deprecation notice

# Next steps

A follow-up PR can remove the legacy notify path entirely once we are
ready for the clean drop. Before then, we can watch
`codex.notify.configured` and `codex.notify.run` to understand the
deprecation impact and remaining active usage. The cleanup PR should
then delete the `notify` config field, the `legacy_notify`
implementation, the old compatibility dispatch types and callsites that
only exist for the legacy path, and the remaining compatibility
docs/tests.

# Testing

- `cargo test -p codex-hooks`
- `cargo test -p codex-config`
- `cargo test -p codex-core emits_deprecation_notice_for_notify`
This commit is contained in:
Abhinav authored and GitHub committed 2026-05-01 17:35:21 +00:00
1 parent 9b8d585075
commit 78baa20780
10 files changed
+72 -159

No files matched your search

+2 -2
View File
@@ -479,7 +479,7 @@ pub struct Config {
/// - `Some("...")`: use the provided attribution text verbatim
pub commit_attribution: Option<String>,
/// Optional external notifier command. When set, Codex will spawn this
/// Deprecated optional external notifier command. When set, Codex will spawn this
/// program after each completed *turn* (i.e. when the agent finishes
/// processing a user submission). The value must be the full command
/// broken into argv tokens **without** the trailing JSON argument - Codex
@@ -498,7 +498,7 @@ pub struct Config {
/// notify-send Codex '{"type":"agent-turn-complete","turn-id":"12345"}'
/// ```
///
/// If unset the feature is disabled.
/// If unset the feature is disabled. Use lifecycle hooks for new automation.
pub notify: Option<Vec<String>>,
/// TUI notification settings, including enabled events, delivery method, and focus condition.
+22
View File
@@ -1,5 +1,6 @@
use super::*;
use crate::goals::GoalRuntimeState;
use codex_otel::LEGACY_NOTIFY_CONFIGURED_METRIC;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSpecialPath;
use tokio::sync::Semaphore;
@@ -576,6 +577,24 @@ impl Session {
}),
});
}
let legacy_notify_configured = config
.notify
.as_ref()
.is_some_and(|argv| !argv.is_empty() && !argv[0].is_empty());
if legacy_notify_configured {
post_session_configured_events.push(Event {
id: INITIAL_SUBMIT_ID.to_owned(),
msg: EventMsg::DeprecationNotice(DeprecationNoticeEvent {
summary:
"`notify` is deprecated and will be removed in a future release."
.to_string(),
details: Some(
"Switch to a `Stop` hook for end-of-turn automation. See https://developers.openai.com/codex/hooks."
.to_string(),
),
}),
});
}
for message in &config.startup_warnings {
post_session_configured_events.push(Event {
id: "".to_owned(),
@@ -633,6 +652,9 @@ impl Session {
if let Some(service_name) = session_configuration.metrics_service_name.as_deref() {
session_telemetry = session_telemetry.with_metrics_service_name(service_name);
}
if legacy_notify_configured {
session_telemetry.counter(LEGACY_NOTIFY_CONFIGURED_METRIC, /*inc*/ 1, &[]);
}
let network_proxy_audit_metadata = NetworkProxyAuditMetadata {
conversation_id: Some(conversation_id.to_string()),
app_version: Some(env!("CARGO_PKG_VERSION").to_string()),
+8
View File
@@ -71,6 +71,7 @@ use codex_hooks::HookEvent;
use codex_hooks::HookEventAfterAgent;
use codex_hooks::HookPayload;
use codex_hooks::HookResult;
use codex_otel::LEGACY_NOTIFY_RUN_METRIC;
use codex_protocol::config_types::ModeKind;
use codex_protocol::error::CodexErr;
use codex_protocol::error::Result as CodexResult;
@@ -575,6 +576,13 @@ pub(crate) async fn run_turn(
},
})
.await;
if !hook_outcomes.is_empty() {
turn_context.session_telemetry.counter(
LEGACY_NOTIFY_RUN_METRIC,
/*inc*/ 1,
&[],
);
}
let mut abort_message = None;
for hook_outcome in hook_outcomes {