From cc1944f2b735034709358effe381b2f4ef3bd33d Mon Sep 17 00:00:00 2001 From: sunerpy Date: Mon, 20 Jul 2026 00:22:45 +0800 Subject: [PATCH] fix(installer): trim codegraph skill description under Codex's 1024-char limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex's skill loader (core-skills/src/loader.rs) enforces MAX_DESCRIPTION_LEN=1024 chars on a skill's front-matter description (after collapsing whitespace to single spaces). An over-limit description becomes an InvalidField parse error that routes the skill into outcome.errors instead of outcome.skills, so the skill silently disappears from Codex's skill list with NO warning. The codegraph SKILL.md description folded to 1026 chars — 2 over — so codegraph was invisible in Codex while sibling skills in the same ~/.agents/skills dir loaded fine. Trim the description to 706 folded chars (preserving the core trigger intent and a representative CJK trigger subset) and add a fail-closed unit test that replicates Codex's fold algorithm and asserts the folded length stays <=1024 (Codex's contract) and <=950 (maintenance margin), so this can never regress. The skill install path (~/.agents/skills) is unchanged and correct — it is Codex's current user-scope skills root; only the over-long description was at fault. --- Cargo.lock | 20 ++--- crates/codegraph-cli/src/installer/skill.rs | 91 +++++++++++++++++++++ skills/codegraph/SKILL.md | 23 ++---- 3 files changed, 110 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d33d6e..6d13893 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,7 +432,7 @@ dependencies = [ [[package]] name = "codegraph-bench" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "clap", @@ -447,7 +447,7 @@ dependencies = [ [[package]] name = "codegraph-core" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "serde", @@ -463,7 +463,7 @@ dependencies = [ [[package]] name = "codegraph-daemon" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -483,7 +483,7 @@ dependencies = [ [[package]] name = "codegraph-extract" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -531,7 +531,7 @@ dependencies = [ [[package]] name = "codegraph-graph" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -545,7 +545,7 @@ dependencies = [ [[package]] name = "codegraph-mcp" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "axum", @@ -568,7 +568,7 @@ dependencies = [ [[package]] name = "codegraph-resolve" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -582,7 +582,7 @@ dependencies = [ [[package]] name = "codegraph-rs" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "assert_cmd", @@ -618,7 +618,7 @@ dependencies = [ [[package]] name = "codegraph-store" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", @@ -630,7 +630,7 @@ dependencies = [ [[package]] name = "codegraph-watch" -version = "0.38.0" +version = "0.40.0" dependencies = [ "anyhow", "codegraph-core", diff --git a/crates/codegraph-cli/src/installer/skill.rs b/crates/codegraph-cli/src/installer/skill.rs index d8f68c0..7c17aa2 100644 --- a/crates/codegraph-cli/src/installer/skill.rs +++ b/crates/codegraph-cli/src/installer/skill.rs @@ -396,6 +396,97 @@ mod tests { ); } + // --- Codex MAX_DESCRIPTION_LEN guard -------------------------------------- + + /// Codex's `core-skills/src/loader.rs` rejects any skill whose YAML + /// front-matter `description`, after `sanitize_single_line` (split on + /// whitespace and rejoin with single spaces), exceeds `MAX_DESCRIPTION_LEN` + /// (1024 chars). An over-limit description is an `InvalidField` parse + /// error, which routes the skill into `outcome.errors` (not + /// `outcome.skills`) with NO warning surfaced to the user — the skill just + /// silently disappears from the agent's skill list. This test fails + /// closed: it asserts the front-matter fence, the single folded + /// `description: >` block, and its extracted content are all present and + /// non-empty before checking the length, so a change in shape (e.g. losing + /// the fence or the scalar) fails the test rather than the assertion being + /// skipped. + #[test] + fn skill_description_within_codex_limit() { + let content = SKILL_MD; + assert!( + content.starts_with("---\n"), + "SKILL.md must open with a YAML front-matter fence" + ); + let after_open = &content[4..]; + let close_idx = after_open + .find("\n---") + .expect("SKILL.md must have a closing front-matter fence"); + let front_matter = &after_open[..close_idx]; + + let lines: Vec<&str> = front_matter.lines().collect(); + let desc_start = lines + .iter() + .position(|line| { + let trimmed = line.trim_start(); + trimmed.starts_with("description:") && trimmed["description:".len()..].trim() == ">" + }) + .expect("front-matter must have exactly one `description: >` folded scalar"); + assert!( + lines + .iter() + .filter(|line| line.trim_start().starts_with("description:")) + .count() + == 1, + "expected exactly one `description:` key in the front-matter" + ); + + let key_re_is_top_level = |line: &str| -> bool { + let mut chars = line.chars(); + match chars.next() { + Some(c) if c.is_ascii_alphanumeric() || c == '_' || c == '-' => {} + _ => return false, + } + line.contains(':') + && line + .split(':') + .next() + .unwrap() + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') + }; + + let mut block_lines = Vec::new(); + for line in &lines[desc_start + 1..] { + if key_re_is_top_level(line) { + break; + } + block_lines.push(*line); + } + assert!( + !block_lines.is_empty(), + "description block must not be empty (extraction failed closed)" + ); + + // Replicates Codex's `sanitize_single_line`: split on whitespace, join + // with single spaces. For a `description: >` folded scalar this is + // identical to what YAML parsing + sanitize would produce. + let joined = block_lines.join(" "); + let folded = joined.split_whitespace().collect::>().join(" "); + + let len = folded.chars().count(); + assert!( + len <= 1024, + "description folds to {len} chars, exceeding Codex's \ + MAX_DESCRIPTION_LEN=1024 — the skill would be silently dropped \ + from Codex's skill list" + ); + assert!( + len <= 950, + "description folds to {len} chars, exceeding the 950-char \ + maintenance margin kept below Codex's 1024 hard limit" + ); + } + // --- decide(): all six branches ------------------------------------------ #[test] diff --git a/skills/codegraph/SKILL.md b/skills/codegraph/SKILL.md index de2061a..906e050 100644 --- a/skills/codegraph/SKILL.md +++ b/skills/codegraph/SKILL.md @@ -2,20 +2,15 @@ name: codegraph description: > Use CodeGraph for ALL codebase navigation and code research on any indexed - project. Reach for it — even when the user doesn't say "codegraph" — on any - of these intents: "how does X work", "who calls X", "what breaks if I change - X", "where is X defined", tracing a call flow, onboarding an unfamiliar - repo, surveying an area, or any whole-project analysis: "analyze the - project", "analyze the code", "understand the codebase", "explain the - architecture", "give me an overview of this repo", "what does this project - do", "survey the code". Also trigger when the user asks to index or - initialize a codebase for an agent, or when .codegraph/ is present in the - repo root. Prefer the codegraph_* tools over grep, find, or the Read tool - whenever source files are involved — one codegraph call returns verbatim - source plus the structural graph and replaces dozens of grep+read - round-trips. Chinese equivalents that should also trigger this skill: - "分析项目代码", "分析项目结构", "分析这个仓库", "看看这个项目", "梳理代码", - "项目结构", "代码分析", "这个项目是做什么的", "介绍下这个项目". + project. Reach for it even when the user doesn't say "codegraph" — for + "how does X work", "who calls X", "what breaks if I change X", "where is X + defined", tracing a call flow, onboarding/surveying an area, or + whole-project analysis: "analyze the project", "explain the architecture", + "what does this project do". Also trigger when asked to index/initialize a + codebase, or when .codegraph/ is present. Prefer codegraph_* tools over + grep, find, or Read on source files — one call returns verbatim source plus + the structural graph, replacing dozens of grep+read round-trips. Chinese + triggers: "分析项目代码", "分析这个仓库", "代码分析", "这个项目是做什么的". --- # CodeGraph — Agent Skill