Skip to content

Resumed threads keep stale model-visible skills after new skills are added #16607

@isCopyman

Description

@isCopyman

What variant of Codex are you using?

CLI / TUI (also likely affects other app-server-backed resume paths)

What issue are you seeing?

Resumed threads keep a stale model-visible skills list after new skills are added.

More specifically:

  • a newly added skill can show up in the UI skill list
  • explicit $skill-name invocation can still work
  • but after codex resume <thread-id>, the model-visible ## Skills context remains stuck on the older snapshot
  • asking the resumed thread what skills are available omits the newly added skill
  • implicit skill triggering does not pick up the new skill either

This makes it look like skill hot reload only updates the display/explicit invocation layer, but not the developer-context skill section that the model actually sees on resumed threads.

What steps can reproduce the bug?

One reliable repro is:

  1. Start a thread with some initial skill set.
  2. Add a new skill afterward (either a custom skill or a plugin-provided skill).
  3. Confirm the new skill is discoverable in a fresh session.
  4. Resume the old thread with codex resume <thread-id>.
  5. Ask the resumed thread what skills are available, or rely on implicit skill triggering.

Observed result:

  • the resumed thread still reports the old skill set
  • the new skill is missing from the model-visible skill inventory
  • explicit $new-skill can still work
  • reinstalling/reloading the plugin does not fix the already-existing resumed thread

What is the expected behavior?

When a thread is resumed, the model-visible skill inventory should reflect the current discovered skills, not the stale set from when the thread was originally created.

At minimum, resumed threads should re-inject the ## Skills developer section whenever the available skills have changed.

Additional information

This appears to be a deeper issue than the previously closed skill hot-reload reports.

Relevant code path analysis:

  1. Each turn does recompute skills:

    • core/src/codex.rs
    • plugins_manager.plugins_for_config(&per_turn_config)
    • skills_load_input_from_config(&per_turn_config, effective_skill_roots)
    • skills_manager.skills_for_config(&skills_input)
  2. But model-visible skills are rendered only through build_initial_context():

    • core/src/codex.rs around build_initial_context
    • turn_context.turn_skills.outcome.allowed_skills_for_implicit_invocation()
    • render_skills_section(&implicit_skills)
  3. In the normal runtime path, record_context_updates_and_set_reference_context_item() does this:

let should_inject_full_context = reference_context_item.is_none();
let context_items = if should_inject_full_context {
    self.build_initial_context(turn_context).await
} else {
    self.build_settings_update_items(reference_context_item.as_ref(), turn_context).await
};

So if a resumed thread reconstructs a non-None reference_context_item, it skips build_initial_context() and only emits settings diffs.

  1. Meanwhile SkillsUpdateAvailable in the TUI appears to refresh the UI-side list only:
    • tui/src/chatwidget.rs
    • on EventMsg::SkillsUpdateAvailable it calls AppCommand::list_skills(..., force_reload = true)
    • explicit $skill then works because it goes through UserInput::Skill

That would explain the observed split:

  • UI skill list updates
  • explicit $skill works
  • resumed thread's developer ## Skills context stays stale
  • implicit invocation does not see the new skill

This seems related to, but stronger/more specific than, #14785.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingskillsIssues related to skills

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions