Compare commits

...

3 Commits

Author SHA1 Message Date
canvrno-oai
0d8e0bed38 Merge branch 'main' into codex/dedupe-skill-load-warnings 2026-05-11 17:27:41 -07:00
canvrno-oai
bbeec013d4 Merge branch 'main' into codex/dedupe-skill-load-warnings 2026-05-11 16:32:19 -07:00
canvrno-oai
d80e27f888 Deduplicate invalid skill load warnings 2026-05-05 17:16:56 -07:00
4 changed files with 80 additions and 1 deletions

View File

@@ -485,6 +485,7 @@ pub(crate) struct App {
status_line_invalid_items_warned: Arc<AtomicBool>,
// Shared across ChatWidget instances so invalid terminal-title config warnings only emit once.
terminal_title_invalid_items_warned: Arc<AtomicBool>,
skill_load_warnings_by_cwd: HashMap<PathBuf, Vec<SkillErrorInfo>>,
// Esc-backtracking state grouped
pub(crate) backtrack: crate::app_backtrack::BacktrackState,
@@ -912,6 +913,7 @@ See the Codex keymap documentation for supported actions and examples."
commit_anim_running: Arc::new(AtomicBool::new(false)),
status_line_invalid_items_warned: status_line_invalid_items_warned.clone(),
terminal_title_invalid_items_warned: terminal_title_invalid_items_warned.clone(),
skill_load_warnings_by_cwd: HashMap::new(),
backtrack: BacktrackState::default(),
backtrack_render_pending: false,
feedback: feedback.clone(),

View File

@@ -39,6 +39,7 @@ pub(super) async fn make_test_app() -> App {
commit_anim_running: Arc::new(AtomicBool::new(false)),
status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)),
terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)),
skill_load_warnings_by_cwd: HashMap::new(),
backtrack: BacktrackState::default(),
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),

View File

@@ -2512,6 +2512,61 @@ async fn replay_snapshot_with_pending_request_suppresses_replay_notices() {
);
}
#[tokio::test]
async fn repeated_skill_load_warnings_emit_once_until_errors_clear() {
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
let cwd = app.chat_widget.config_ref().cwd.to_path_buf();
let error = codex_app_server_protocol::SkillErrorInfo {
path: test_path_buf("/tmp/user/skills/planning/SKILL.md"),
message: "invalid YAML".to_string(),
};
let response = codex_app_server_protocol::SkillsListResponse {
data: vec![codex_app_server_protocol::SkillsListEntry {
cwd: cwd.clone(),
skills: Vec::new(),
errors: vec![error],
}],
};
app.handle_skills_list_response(response.clone());
app.handle_skills_list_response(response.clone());
assert_eq!(
drain_insert_history_transcripts(&mut app_event_rx),
vec![
"⚠ Skipped loading 1 skill(s) due to invalid SKILL.md files.".to_string(),
"⚠ /tmp/user/skills/planning/SKILL.md: invalid YAML".to_string(),
],
);
app.handle_skills_list_response(codex_app_server_protocol::SkillsListResponse {
data: vec![codex_app_server_protocol::SkillsListEntry {
cwd,
skills: Vec::new(),
errors: Vec::new(),
}],
});
assert_eq!(
drain_insert_history_transcripts(&mut app_event_rx),
Vec::<String>::new(),
);
app.handle_skills_list_response(response);
assert_eq!(drain_insert_history_transcripts(&mut app_event_rx).len(), 2);
}
fn drain_insert_history_transcripts(
app_event_rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
) -> Vec<String> {
let mut transcripts = Vec::new();
while let Ok(event) = app_event_rx.try_recv() {
if let AppEvent::InsertHistoryCell(cell) = event {
transcripts.push(lines_to_single_string(&cell.transcript_lines(/*width*/ 80)));
}
}
transcripts
}
#[tokio::test]
async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()> {
let mut app = make_test_app().await;
@@ -3985,6 +4040,7 @@ async fn make_test_app() -> App {
commit_anim_running: Arc::new(AtomicBool::new(false)),
status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)),
terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)),
skill_load_warnings_by_cwd: HashMap::new(),
backtrack: BacktrackState::default(),
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),
@@ -4048,6 +4104,7 @@ async fn make_test_app_with_channels() -> (
commit_anim_running: Arc::new(AtomicBool::new(false)),
status_line_invalid_items_warned: Arc::new(AtomicBool::new(false)),
terminal_title_invalid_items_warned: Arc::new(AtomicBool::new(false)),
skill_load_warnings_by_cwd: HashMap::new(),
backtrack: BacktrackState::default(),
backtrack_render_pending: false,
feedback: codex_feedback::CodexFeedback::new(),

View File

@@ -1297,10 +1297,29 @@ impl App {
pub(super) fn handle_skills_list_response(&mut self, response: SkillsListResponse) {
let cwd = self.chat_widget.config_ref().cwd.clone();
let errors = errors_for_cwd(&cwd, &response);
emit_skill_load_warnings(&self.app_event_tx, &errors);
self.emit_skill_load_warnings_if_changed(&cwd, errors);
self.chat_widget.handle_skills_list_response(response);
}
fn emit_skill_load_warnings_if_changed(&mut self, cwd: &Path, errors: Vec<SkillErrorInfo>) {
if errors.is_empty() {
self.skill_load_warnings_by_cwd.remove(cwd);
return;
}
if self
.skill_load_warnings_by_cwd
.get(cwd)
.is_some_and(|previous_errors| previous_errors == &errors)
{
return;
}
emit_skill_load_warnings(&self.app_event_tx, &errors);
self.skill_load_warnings_by_cwd
.insert(cwd.to_path_buf(), errors);
}
pub(super) async fn handle_thread_rollback_response(
&mut self,
thread_id: ThreadId,