Make loading malformed skills fail-open (#8243)

Instead of failing to start Codex, clearly call out that N skills did
not load and provide warnings so that the user may fix them.

<img width="3548" height="874" alt="image"
src="https://github.com/user-attachments/assets/6ce041b2-1373-4007-a6dd-0194e58fafe4"
/>
This commit is contained in:
Gav Verma authored and GitHub committed 2025-12-17 23:41:04 -08:00
1 parent da3869eeb6
commit 50dafbc31b
8 files changed
+44 -455

No files matched your search

+22 -25
View File
@@ -17,8 +17,6 @@ use crate::pager_overlay::Overlay;
use crate::render::highlight::highlight_bash_to_lines;
use crate::render::renderable::Renderable;
use crate::resume_picker::ResumeSelection;
use crate::skill_error_prompt::SkillErrorPromptOutcome;
use crate::skill_error_prompt::run_skill_error_prompt;
use crate::tui;
use crate::tui::TuiEvent;
use crate::tui::scrolling::TranscriptLineMeta;
@@ -44,7 +42,6 @@ use codex_core::protocol::Op;
use codex_core::protocol::SessionSource;
use codex_core::protocol::SkillErrorInfo;
use codex_core::protocol::TokenUsage;
use codex_core::skills::SkillError;
use codex_protocol::ConversationId;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ModelUpgrade;
@@ -118,16 +115,6 @@ fn session_summary(
})
}
fn skill_errors_from_info(errors: &[SkillErrorInfo]) -> Vec<SkillError> {
errors
.iter()
.map(|err| SkillError {
path: err.path.clone(),
message: err.message.clone(),
})
.collect()
}
fn errors_for_cwd(cwd: &Path, response: &ListSkillsResponseEvent) -> Vec<SkillErrorInfo> {
response
.skills
@@ -137,6 +124,27 @@ fn errors_for_cwd(cwd: &Path, response: &ListSkillsResponseEvent) -> Vec<SkillEr
.unwrap_or_default()
}
fn emit_skill_load_warnings(app_event_tx: &AppEventSender, errors: &[SkillErrorInfo]) {
if errors.is_empty() {
return;
}
let error_count = errors.len();
app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
crate::history_cell::new_warning_event(format!(
"Skipped loading {error_count} skill(s) due to invalid SKILL.md files."
)),
)));
for error in errors {
let path = error.path.display();
let message = error.message.as_str();
app_event_tx.send(AppEvent::InsertHistoryCell(Box::new(
crate::history_cell::new_warning_event(format!("{path}: {message}")),
)));
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct SessionSummary {
usage_line: String,
@@ -1547,18 +1555,7 @@ impl App {
if let EventMsg::ListSkillsResponse(response) = &event.msg {
let cwd = self.chat_widget.config_ref().cwd.clone();
let errors = errors_for_cwd(&cwd, response);
if errors.is_empty() {
self.chat_widget.handle_codex_event(event);
return Ok(true);
}
let errors = skill_errors_from_info(&errors);
match run_skill_error_prompt(tui, &errors).await {
SkillErrorPromptOutcome::Exit => {
self.chat_widget.submit_op(Op::Shutdown);
return Ok(false);
}
SkillErrorPromptOutcome::Continue => {}
}
emit_skill_load_warnings(&self.app_event_tx, &errors);
}
self.chat_widget.handle_codex_event(event);
}