Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

codex-skills-extension: pass ModelInfo context window in SkillsExtension::contribute_thread_context

Open Beginner friendly
#54,128 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
ai

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

bug CLI context skills

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 CLI 0.162.0-alpha.17.2)
  • OS: macOS aarch64

Root-Cause Analysis & Verification

Summary

  • Reads model_info once from thread_store.get::<ModelInfo>() in SkillsExtension::contribute_thread_context (codex-rs/ext/skills/src/extension.rs).
  • Passes model_info.as_deref().and_then(ModelInfo::resolved_context_window) to skill_metadata_budget(context_window, config.max_context_tokens) so CatalogSurface::ThreadContext respects the model's context window just like contribute_world_state.
  • Adds behavioral test contribute_thread_context_uses_model_context_window_for_custom_provider_budget in codex-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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openai/codex

All issues in openai/codex

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.