From 86ba270926eacf4f4cd499b26e4e4e37fdcb1820 Mon Sep 17 00:00:00 2001 From: Thibault Sottiaux Date: Wed, 15 Oct 2025 22:24:46 -0700 Subject: [PATCH] fix: skip /init when AGENTS.md already exists (#5242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change aborts /init if an AGENTS.md already exists to avoid plainly overwriting it. Screenshot 2025-10-15 at 9 43 07 PM --- codex-rs/tui/src/chatwidget.rs | 9 ++++++++ codex-rs/tui/src/chatwidget/tests.rs | 33 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index bd0893bc..88249f13 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -7,6 +7,7 @@ use codex_core::config::Config; use codex_core::config_types::Notifications; use codex_core::git_info::current_branch_name; use codex_core::git_info::local_git_branches; +use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME; use codex_core::protocol::AgentMessageDeltaEvent; use codex_core::protocol::AgentMessageEvent; use codex_core::protocol::AgentReasoningDeltaEvent; @@ -1130,6 +1131,14 @@ impl ChatWidget { self.app_event_tx.send(AppEvent::NewSession); } SlashCommand::Init => { + let init_target = self.config.cwd.join(DEFAULT_PROJECT_DOC_FILENAME); + if init_target.exists() { + let message = format!( + "{DEFAULT_PROJECT_DOC_FILENAME} already exists here. Skipping /init to avoid overwriting it." + ); + self.add_info_message(message, None); + return; + } const INIT_PROMPT: &str = include_str!("../prompt_for_init_command.md"); self.submit_text_message(INIT_PROMPT.to_string()); } diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 64130d55..a9102fdb 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -49,6 +49,7 @@ use std::io::BufRead; use std::io::BufReader; use std::path::PathBuf; use tempfile::NamedTempFile; +use tempfile::tempdir; use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::unbounded_channel; @@ -769,6 +770,38 @@ fn review_popup_custom_prompt_action_sends_event() { assert!(found, "expected OpenReviewCustomPrompt event to be sent"); } +#[test] +fn slash_init_skips_when_project_doc_exists() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(); + let tempdir = tempdir().unwrap(); + let existing_path = tempdir.path().join(DEFAULT_PROJECT_DOC_FILENAME); + std::fs::write(&existing_path, "existing instructions").unwrap(); + chat.config.cwd = tempdir.path().to_path_buf(); + + chat.dispatch_command(SlashCommand::Init); + + match op_rx.try_recv() { + Err(TryRecvError::Empty) => {} + other => panic!("expected no Codex op to be sent, got {other:?}"), + } + + let cells = drain_insert_history(&mut rx); + assert_eq!(cells.len(), 1, "expected one info message"); + let rendered = lines_to_single_string(&cells[0]); + assert!( + rendered.contains(DEFAULT_PROJECT_DOC_FILENAME), + "info message should mention the existing file: {rendered:?}" + ); + assert!( + rendered.contains("Skipping /init"), + "info message should explain why /init was skipped: {rendered:?}" + ); + assert_eq!( + std::fs::read_to_string(existing_path).unwrap(), + "existing instructions" + ); +} + /// The commit picker shows only commit subjects (no timestamps). #[test] fn review_commit_picker_shows_subjects_without_timestamps() {