codex-skills-extension: pass ModelInfo context window in SkillsExtension::contribute_thread_context
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 68/100
Research direction
The bug is in codex-rs/ext/skills/src/extension.rs, inside contribute_thread_context, where the budget call passes None for the context window even though ModelInfo is already read from thread_store. Start by reading how contribute_world_state derives its context window from model_info, then check the existing tests in codex-rs/ext/skills/tests/skills_extension.rs. Done when a custom provider's thread-context rendering respects resolved_context_window() and cargo test -p codex-skills-extension --test skills_extension passes.
Written by the indexing model from the issue text.
Description
Summary
In codex-rs/ext/skills/src/extension.rs, SkillsExtension::contribute_world_state scales the skill catalog token/character budget to the active model's context window via:
skill_metadata_budget(
input.model_info.resolved_context_window(),
config.max_context_tokens,
)
However, SkillsExtension::contribute_thread_context (codex-rs/ext/skills/src/extension.rs:234) hardcodes /*context_window*/ None:
skill_metadata_budget(/*context_window*/ None, config.max_context_tokens)
even though thread_store.get::<ModelInfo>() is already accessed on line 222 of the same method. Consequently, custom skill providers rendered on the CatalogSurface::ThreadContext surface always use DEFAULT_SKILL_METADATA_BUDGET instead of scaling to the thread model's resolved_context_window().
Expected Behavior
SkillsExtension::contribute_thread_context should extract model_info.as_deref().and_then(ModelInfo::resolved_context_window) from thread_store.get::<ModelInfo>() and pass it to skill_metadata_budget(context_window, config.max_context_tokens).
Environment
- Codex CLI version:
0.162.1(aarch64-apple-darwin) - Codex Desktop version:
26.1007.71326(embedded CLI0.162.0-alpha.17.2) - OS: macOS
aarch64
Root-Cause Analysis & Verification
Summary
- Reads
model_infoonce fromthread_store.get::<ModelInfo>()inSkillsExtension::contribute_thread_context(codex-rs/ext/skills/src/extension.rs). - Passes
model_info.as_deref().and_then(ModelInfo::resolved_context_window)toskill_metadata_budget(context_window, config.max_context_tokens)soCatalogSurface::ThreadContextrespects the model's context window just likecontribute_world_state. - Adds behavioral test
contribute_thread_context_uses_model_context_window_for_custom_provider_budgetincodex-rs/ext/skills/tests/skills_extension.rs.
Verification
cargo test --offline -p codex-skills-extension --test skills_extension(21 passed)
Reference Patch & Regression Tests (0002-fix-skills-pass-model-context-window-in-contribute-thread-context.patch)
From 852b78b2801e7000c4392bbf01839584b8e90d28 Mon Sep 17 00:00:00 2001
From: trukhinyuri <[email protected]>
Date: Thu, 15 Oct 2026 01:05:51 +0200
Subject: [PATCH] fix(skills): pass model context window in
contribute_thread_context
---
codex-rs/ext/skills/src/extension.rs | 10 ++-
codex-rs/ext/skills/tests/skills_extension.rs | 71 +++++++++++++++++++
2 files changed, 78 insertions(+), 3 deletions(-)
diff --git a/codex-rs/ext/skills/src/extension.rs b/codex-rs/ext/skills/src/extension.rs
index e2400b9..85cbb20 100644
--- a/codex-rs/ext/skills/src/extension.rs
+++ b/codex-rs/ext/skills/src/extension.rs
@@ -219,9 +219,13 @@ where
for warning in bounded_warnings(&catalog.warnings) {
self.emit_warning(thread_store.level_id(), /*turn_id*/ None, warning);
}
- let include_usage = thread_store
- .get::<ModelInfo>()
+ let model_info = thread_store.get::<ModelInfo>();
+ let include_usage = model_info
+ .as_deref()
.is_some_and(|model_info| model_info.include_skills_usage_instructions);
+ let context_window = model_info
+ .as_deref()
+ .and_then(ModelInfo::resolved_context_window);
let extension_metrics = session_store
.get::<SkillsSessionState>()
.and_then(|state| state.extension_metrics.clone());
@@ -231,7 +235,7 @@ where
&catalog,
include_usage,
SkillCatalogRenderPolicy::ExtensionCompatible,
- skill_metadata_budget(/*context_window*/ None, config.max_context_tokens),
+ skill_metadata_budget(context_window, config.max_context_tokens),
);
if let Some(message) = rendered.warning_message {
self.emit_warning(thread_store.level_id(), /*turn_id*/ None, message);
diff --git a/codex-rs/ext/skills/tests/skills_extension.rs b/codex-rs/ext/skills/tests/skills_extension.rs
index b0ee66b..1a16801 100644
--- a/codex-rs/ext/skills/tests/skills_extension.rs
+++ b/codex-rs/ext/skills/tests/skills_extension.rs
@@ -57,6 +57,7 @@ use codex_skills_extension::HostSkillsService;
use codex_skills_extension::HostSkillsSnapshot;
use codex_skills_extension::InjectedHostSkillPrompts;
use codex_skills_extension::SkillLoadOutcome;
+use codex_skills_extension::SkillProviderSource;
use codex_skills_extension::SkillProviders;
use codex_skills_extension::SkillsExtensionConfig;
use codex_skills_extension::catalog::SkillAuthority;
@@ -2687,3 +2688,73 @@ fn read_request_keys(
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
}
+
+#[tokio::test]
+async fn contribute_thread_context_uses_model_context_window_for_custom_provider_budget()
+-> TestResult {
+ let custom_entries = (0..200)
+ .map(|index| {
+ test_entry(
+ SkillSourceKind::Custom("custom".to_string()),
+ "custom-authority",
+ &format!("custom/skill-{index:02}"),
+ &format!("skill-{index:02}/SKILL.md"),
+ )
+ })
+ .collect();
+ let providers = SkillProviders::new().with_provider(SkillProviderSource::new(
+ SkillSourceKind::Custom("custom".to_string()),
+ "custom",
+ Arc::new(StaticSkillProvider {
+ catalog: SkillCatalog {
+ entries: custom_entries,
+ warnings: Vec::new(),
+ },
+ read_requests: Arc::new(Mutex::new(Vec::new())),
+ list_calls: None,
+ fail_first_list: false,
+ }),
+ ));
+ let (event_tx, event_rx) = std::sync::mpsc::channel();
+ let mut builder =
+ ExtensionRegistryBuilder::with_event_sink(Arc::new(ChannelEventSink(event_tx)));
+ install_with_providers(&mut builder, providers, skills_extension_config);
+ let registry = builder.build();
+ let session_store = ExtensionData::new("session");
+ let thread_store = ExtensionData::new("thread");
+ let mut config = default_config();
+ config.bundled_skills_enabled = false;
+ registry.thread_lifecycle_contributors()[0]
+ .on_thread_start(ThreadStartInput {
+ config: &config,
+ session_source: &SessionSource::Cli,
+ persistent_thread_state_available: true,
+ environments: &[],
+ mcp_resource_client: None,
+ extension_metrics: None,
+ session_store: &session_store,
+ thread_store: &thread_store,
+ })
+ .await;
+ let mut model_info = model_info_from_slug("test-model");
+ model_info.context_window = Some(10_000);
+ thread_store.insert(model_info);
+
+ let thread_fragments = registry.context_contributors()[0]
+ .contribute_thread_context(&session_store, &thread_store)
+ .await;
+ assert_eq!(thread_fragments.len(), 1);
+ let rendered = thread_fragments[0].text();
+ assert!(!rendered.contains("skill-39"));
+ assert!(rendered.contains("additional skills omitted"));
+
+ let warning = event_rx.try_recv()?.into_warning();
+ assert_eq!(warning.thread_id, thread_store.level_id());
+ assert_eq!(warning.turn_id, None);
+ assert!(
+ warning
+ .message
+ .starts_with("Exceeded skills context budget.")
+ );
+ Ok(())
+}
--
2.56.0
- Dominant language
- Rust
- Stars
- 129k
- Forks
- 20.3k
- Avg merge
- 1m
- Merged PRs (30d)
- 998
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openai/codex
-
Python SDK: CodexClient.close() leaves the app-server's stdout/stderr pipes open (and never waits after kill)Possibly taken @akhil9tiet claimed this today. Openapp-server bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
openai/codex#54908 · 2 comments ·
Maintainers usually reply within 1 day
-
bug CLI tool-calls windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
bug tool-calls
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
Clarification about NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S in the VS Code extension environmentOpendocumentation extension windows-os
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
openai/codex#54780 · 1 comment ·
Maintainers usually reply within 1 day
-
bug context
Difficulty 2/5 Under an hour Newbie friendliness 74/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
[Bug] ComponentType classifies 0x8A8A-0xEAEA as GREASE; mls-extensions §7.5 makes them private useOpen
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
area:ecosystem documentation
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Chelis-Lang/chelis#3866 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
metasequoiaime/msime#8620 ·
Maintainers usually reply within 1 day
-
agent-detection macos p2 triaged
Difficulty 1/5 Under an hour Newbie friendliness 90/100
Maintainers usually reply within 1 day