fix(skills): a skill body stops at the 25k-token read cap

`read_file` exempted SKILL.md from every limit, and both injection paths
(`/skill` expansion and the `skill` tool) pasted the whole body. A 220 KB
skill put 55k tokens into the context on each call.

`cap_skill_body` cuts an over-cap body at the last line boundary that
fits (a char boundary when the body is one long line) and appends a note
that points at offset and limit. It runs before `apply_substitutions`, so
the arguments land after the cut. Both injection paths call it and log
the cut.

`read_file` returns a SKILL.md whole only while it fits the cap. Over the
cap it reads like any other file: offset and limit apply, and a window
that is still too large answers FileTooLarge. Without that the note would
send the model to a read that ignores its range.

`skill_file_skips_token_limit` pinned the exemption; it gives way to
`skill_file_over_cap_returns_file_too_large` and
`skill_file_over_cap_honors_offset_and_limit`, as upstream did.

Ported from upstream (grok-build tools skills/skill.rs cap_skill_body and
read_file skill_full, 1.0.33). Upstream's `max_output_bytes` read budget
in the same hunk is a feature and stays out.
This commit is contained in:
ZacharyZhang-NY committed 2026-09-19 02:31:24 -04:00
1 parent 443de79e2f
commit 206711bed5
6 files changed
+163 -28

No files matched your search

@@ -6,6 +6,7 @@
use std::path::Path;
use crate::implementations::skills::body_cap::cap_skill_body;
use crate::implementations::skills::skill::{SkillOutput, extract_skill_body, format_skill_name};
use crate::implementations::skills::types::SkillInfo;
use crate::types::requirements::{Expr, ToolRequirement};
@@ -279,7 +280,7 @@ impl kigi_tool_runtime::Tool for SkillTool {
}
};
let content = match load_skill_content(&skill).await {
let mut content = match load_skill_content(&skill).await {
Ok(c) => c,
Err(e) => {
return Ok(SkillOutput {
@@ -292,6 +293,14 @@ impl kigi_tool_runtime::Tool for SkillTool {
}
};
if cap_skill_body(&mut content) {
tracing::info!(
skill = %skill.name,
path = %skill.path,
"skill body truncated at read cap"
);
}
let files = list_skill_files(&skill, 10).await;
let output = format_output(&skill, &content, &files);