TUI /goal recognizes $skill tokens but persists them as inert objective text
What version of Codex CLI is running?
codex-cli 0.144.0
Also visible from the current TUI goal submission, goal persistence, goal continuation, and skills extension paths on main.
What subscription do you have?
Not subscription-specific. Observed on Pro 20x.
Which model were you using?
Observed with GPT-5.5 and GPT-5.6-style model configurations.
This appears to be prompt assembly / structured-input loss rather than model-specific behavior.
What platform is your computer?
Linux 6.17.0-35-generic x86_64
What terminal emulator and version are you using (if applicable)?
Ghostty (TERM=xterm-ghostty), no tmux/zellij.
Codex doctor report
Not applicable. The mismatch is visible between the TUI mention parser, persisted goal schema, goal continuation input, and skill selection path.
What issue are you seeing?
The TUI recognizes and styles a $skill token while a /goal objective is being entered, but goal creation/update preserves only the literal text. It does not preserve or invoke the structured skill selection.
For example:
/goal $supervisor-hardening implement the approved plan and validate every delegated change
The composer recognizes $supervisor-hardening as an installed skill token. After the goal is created, the objective contains the literal token, but the corresponding SKILL.md is not injected into automatic goal turns.
This is particularly visible when the skill has:
policy:
allow_implicit_invocation: false
The skill cannot be recovered through implicit routing, so the goal proceeds without the explicitly selected workflow. The model may see $supervisor-hardening as ordinary text while lacking both the skill body and the explicit-selection metadata needed to load it.
The problem persists through automatic goal continuations because each continuation injects the objective as internal goal context rather than as structured UserInput::Skill / UserInput::Mention input.
What steps can reproduce the bug?
- Install a valid explicit-only skill named
goal-skill-test:
``yaml``
# agents/openai.yaml
policy:
allow_implicit_invocation: false
- Give its
SKILL.mdan observable requirement:
``markdownGOAL-SKILL-ACTIVE
Prefix every goal progress update with .``
- In the TUI, create a goal:
``text``
/goal $goal-skill-test perform a multi-turn task and continue until complete
- Observe that the composer recognizes/styles
$goal-skill-testas a known skill mention. - Let the goal start and automatically continue for another turn.
- Observe that:
- the persisted goal objective contains the literal
$goal-skill-testtoken; - the selected
SKILL.mdbody is not injected; - the observable skill requirement is not followed;
- an explicit-only skill may be treated as missing/unavailable.
- Send
$goal-skill-testas a normal user turn. - Observe that normal explicit skill injection works, demonstrating that the skill is installed and resolvable.
The same mismatch applies when updating an existing goal with a recognized skill mention.
What is the expected behavior?
A skill mention recognized during /goal creation or update should become a structured, goal-scoped skill activation.
Expected lifecycle:
- resolve the mention when the goal is created/updated;
- persist the resolved skill identity with the goal, rather than only preserving
$namein objective prose; - inject the skill instructions into the initial goal turn;
- reinject them into automatic goal continuations and after compaction/resume;
- keep them active until the goal completes, is cleared, is replaced, or the skill is explicitly removed from the goal;
- emit a visible error if a selected skill cannot be resolved or loaded.
The persisted objective can retain the human-readable token, but correctness should not depend on reparsing prose on every continuation.
Additional information
Current source shape:
codex-rs/tui/src/chatwidget/slash_dispatch.rs/goalcreates aGoalDraftcontaining objective text,text_elements, pastes, and images;- it does not carry the normal submitted message's
mention_bindingsinto goal state. codex-rs/tui/src/goal_files.rsmaterialize_goal_draftexpands pastes/images and returns a plain objectiveString;- recognized skill semantics are not part of the returned goal data.
codex-rs/app-server/src/request_processors/thread_goal_processor.rsthread_goal_setpersists objective/status/token budget;- there is no goal-scoped selected-skills field.
codex-rs/ext/goal/src/steering.rs- automatic continuation renders the objective into an internal
source="goal"context item. codex-rs/ext/skills/src/extension.rs- explicit skill selection inspects the current
TurnInputContext.user_input. codex-rs/ext/skills/src/selection.rs- selection resolves
UserInput::Skill, skill-pathUserInput::Mention, or$nameinUserInput::Text; - the internal goal continuation item is not one of those inputs.
So the UI recognizes the token, but the goal persistence and continuation layers erase the structured selection before the skills extension can act on it.
Related but distinct issues:
- #32169: explicit-only skill activation is lost after compaction in long-running tasks.
- #23454: normal explicit
$skillinvocation can fail for explicit-only skills. - #19695: confirms
$<skill-name>is the intended explicit invocation contract. - #22644: the full goal contract, including a required
SKILL.md, can be lost after compaction. - #31659: automatic compaction can make a running goal drift to older conversation state.
- #23496: selected skill instructions involving subagents may be ignored unless repeated in live prompt text.
This report is specifically about /goal accepting a known skill token in the composer but storing it as inert objective text.
Short implementation plan
- Preserve mention bindings/structured skill selections in
GoalDraft. - Resolve skill mentions during goal creation/update and reject or warn on unresolved explicit selections.
- Extend persisted goal state with goal-scoped skill references, for example:
``text``
name
authority/source
package/path
content/config fingerprint
- Make the goal extension contribute those structured selections to the skills extension for:
- the initial goal turn;
- automatic continuation;
- objective updates;
- resume;
- post-compaction context reconstruction.
- Do not rely only on rescanning the rendered
<objective>text. Long objectives may be materialized into a file reference, names can become ambiguous, and installed skill state can change. - Add regression coverage spanning TUI and app-server:
- composer-recognized explicit-only
$skill; /goalcreation and update preserve its resolved identity;- first and subsequent automatic goal turns receive its
SKILL.md; - compaction/resume rehydrate it;
- clearing/completing/replacing the goal removes its activation.