allow_implicit_invocation: false also disables explicit $skill invocation, contradicting docs

Open 💬 1 comment Opened Aug 25, 2026 by keybrdist

Docs (https://developers.openai.com/codex/skills → learn.chatgpt.com/docs/build-skills) state that with agents/openai.yaml:

policy:
  allow_implicit_invocation: false

"the skill is not injected into the model context by default, but can still be invoked explicitly via $skill."

Measured on codex-cli 0.149.0 (macOS), the second half doesn't hold: $skill mentions of a policy-restricted skill are not expanded.

Repro:

  1. Create ~/.codex/skills/test-policy/SKILL.md (name/description frontmatter, body marker POLICY-BODY) and agents/openai.yaml with the policy above.
  2. Create ~/.codex/skills/test-plain/SKILL.md identically, no openai.yaml.
  3. Run codex debug prompt-input '$test-plain go' → skill body is injected.
  4. Run codex debug prompt-input '$test-policy go' → prompt contains only the literal text; no expansion, and the skill is absent from <skills_instructions>.

Same behavior through codex app-server threads. So a skill with the policy has no invocation path at all in non-TUI surfaces.

Expected: either $skill works for policy-restricted skills (docs behavior), or the docs state that allow_implicit_invocation: false disables explicit invocation outside the TUI picker.

View original on GitHub ↗

1 Comment

argszero · 2 days ago

Thanks for the detailed repro — I traced the full skill-invocation pipeline on current main and ran a verification test. Here's what I found:

1. codex debug prompt-input cannot demonstrate explicit $skill invocation — it doesn't run the turn pipeline.

build_prompt_input_from_session (codex-rs/core/src/prompt_debug.rs:88) explicitly notes: "Prompt debugging builds a standalone request without entering run_turn." The explicit-invocation machinery (build_skills_and_pluginscollect_explicit_skill_mentionsload_skill_prompts) only runs inside run_turn (turn.rs:808–826). So in debug prompt-input, no host skill body is ever injected — visible or policy-restricted.

What you observed for test-plain vs test-policy is the <skills_instructions> catalog listing (name/description lines, not skill bodies): test-plain is model-visible so it appears; test-policy has allow_implicit_invocation: false so it's hidden from that list. That asymmetry is exactly the documented behavior — the docs' "not injected by default" half working as intended.

2. On the real turn path, explicit $skill invocation of policy-restricted host skills does work (current main, c51e7b3736):

  • catalog_entry_from_skill (ext/skills/src/provider/host.rs:141–146): !enabled.disabled(); !allows_implicit_invocation().hidden_from_prompt() — which only clears prompt_visible, keeping enabled = true. Being "disabled" comes exclusively from user config rules (resolve_disabled_paths, config/src/skills_config.rs:94–124), never from the policy.
  • Selection filters only enabled: ext selection.rs:72/89 and skills selection.rs:142/165 (is_skill_enabled = not in disabled_paths). Hidden skills are still selected by $name / skill:// paths.
  • load_skill_prompts (ext/skills/src/host_prompt.rs:56–96) has no visibility gate — it reads and injects whatever was selected.

I verified this empirically with a quick unit test on current main (policy-restricted host skill → enabled stays true, is_model_visible() false, and collect_explicit_skill_mentions selects it from $name): passes. This is the same design #36976 (Honor explicit-only orchestrator skills) established for orchestrator skills — hidden from the prompt catalog, still directly invocable.

3. The genuine gap: the host path has no test coverage for this case.

#36976 added explicit_only_orchestrator_skill_is_hidden_but_can_be_invoked (core/tests/suite/skills_extension.rs:821), and the executor path is covered too — but there is no equivalent host-side integration test. That's worth adding to lock in the documented behavior and catch regressions.

4. Version note: 0.149.0 → 0.149.1 is only 5 commits (none touching skills), and the host-skill architecture you hit (#36309 world-state catalog, #37503 prompt injection move) predates 0.149.0 — so the code paths here behave the same on your version.

If you can reproduce non-expansion via the app-server v2 thread / TUI (the real run_turn path) on 0.149.x, that would point to a real bug — happy to dig in. Otherwise, my suggestion would be: add the missing host explicit-only integration test (I can prepare that PR), and optionally note in the codex debug prompt-input help text that it doesn't exercise turn-time skill/plugin injection.