diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31f8f28721f..2cdfc7f2c96 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Agents/Anthropic: send implicit Anthropic beta headers only to direct public Anthropic endpoints, including OAuth, so custom Anthropic-compatible providers no longer mis-handle unsupported beta flags unless explicitly configured. Refs #73346. Thanks @byBrodowski.
+- Skills: require explicit `skills.entries.coding-agent.enabled` before exposing the bundled coding-agent skill, so installs with Codex on PATH but no OpenAI auth do not silently offer Codex delegation. Fixes #73358. Thanks @LaFleurAdvertising and @Sanjays2402.
- Plugins/startup: precompute bundled runtime mirror fingerprints before taking the mirror lock, including dist-runtime canonical roots, so Docker Desktop/WSL cold starts no longer hold `.openclaw-runtime-mirror.lock` while scanning slow persisted volumes. Fixes #73339. Thanks @1yihui.
- Channels/LINE: persist inbound image, video, audio, and file downloads in `~/.openclaw/media/inbound/` instead of temporary files so agents can still read LINE media after `/tmp` cleanup. Fixes #73370. Thanks @hijirii and @wenxu007.
- Control UI/WebChat: keep large attachment payloads out of Lit state and optimistic chat messages, using object URL previews plus send-time payload serialization so PDF/image uploads no longer trigger `RangeError: Maximum call stack size exceeded`. Fixes #73360; refs #54378 and #63432. Thanks @hejunhui-73, @Ansub, and @christianhernandez3-afk.
diff --git a/docs/tools/skills.md b/docs/tools/skills.md
index c8610ad74e1..244bb29d327 100644
--- a/docs/tools/skills.md
+++ b/docs/tools/skills.md
@@ -324,6 +324,10 @@ under `skills.entries` in `~/.openclaw/openclaw.json`:
`false` disables the skill even if it is bundled or installed.
+ The bundled `coding-agent` skill is opt-in: set
+ `skills.entries.coding-agent.enabled: true` before exposing it to agents,
+ then make sure one of `claude`, `codex`, `opencode`, or `pi` is installed and
+ authenticated for its own CLI.
Convenience for skills that declare `metadata.openclaw.primaryEnv`. Supports plaintext or SecretRef.
diff --git a/skills/coding-agent/SKILL.md b/skills/coding-agent/SKILL.md
index 6961bd1ba5e..8d8adb032ff 100644
--- a/skills/coding-agent/SKILL.md
+++ b/skills/coding-agent/SKILL.md
@@ -6,7 +6,11 @@ metadata:
"openclaw":
{
"emoji": "🧩",
- "requires": { "anyBins": ["claude", "codex", "opencode", "pi"] },
+ "requires":
+ {
+ "anyBins": ["claude", "codex", "opencode", "pi"],
+ "config": ["skills.entries.coding-agent.enabled"],
+ },
"install":
[
{
diff --git a/src/agents/skills.buildworkspaceskillstatus.test.ts b/src/agents/skills.buildworkspaceskillstatus.test.ts
index f0445542862..9d20d47b05e 100644
--- a/src/agents/skills.buildworkspaceskillstatus.test.ts
+++ b/src/agents/skills.buildworkspaceskillstatus.test.ts
@@ -7,6 +7,7 @@ import { buildWorkspaceSkillStatus } from "./skills-status.js";
import { writeSkill } from "./skills.e2e-test-helpers.js";
import { createCanonicalFixtureSkill } from "./skills.test-helpers.js";
import type { SkillEntry } from "./skills/types.js";
+import { loadWorkspaceSkillEntries } from "./skills/workspace.js";
const tempDirs: string[] = [];
@@ -166,6 +167,63 @@ describe("buildWorkspaceSkillStatus", () => {
expect(skill?.bundled).toBe(true);
});
+ it("requires explicit enablement before exposing bundled coding-agent", async () => {
+ const workspaceDir = await createTempWorkspaceDir();
+ const bundledSkillsDir = path.resolve("skills");
+ const entries = loadWorkspaceSkillEntries(workspaceDir, {
+ managedSkillsDir: path.join(workspaceDir, ".managed"),
+ bundledSkillsDir,
+ config: {
+ skills: {
+ allowBundled: ["coding-agent"],
+ },
+ },
+ });
+ const codingAgent = entries.find((entry) => entry.skill.name === "coding-agent");
+ expect(codingAgent).toBeDefined();
+
+ const eligibility = {
+ remote: {
+ platforms: [process.platform],
+ hasBin: () => false,
+ hasAnyBin: (bins: string[]) => bins.includes("codex"),
+ },
+ };
+ const defaultReport = withEnv({ PATH: "" }, () =>
+ buildWorkspaceSkillStatus(workspaceDir, {
+ entries: [codingAgent as SkillEntry],
+ config: {
+ skills: {
+ allowBundled: ["coding-agent"],
+ },
+ },
+ eligibility,
+ }),
+ );
+ const defaultStatus = defaultReport.skills[0];
+ expect(defaultStatus?.eligible).toBe(false);
+ expect(defaultStatus?.requirements.config).toEqual(["skills.entries.coding-agent.enabled"]);
+ expect(defaultStatus?.missing.config).toEqual(["skills.entries.coding-agent.enabled"]);
+
+ const enabledReport = withEnv({ PATH: "" }, () =>
+ buildWorkspaceSkillStatus(workspaceDir, {
+ entries: [codingAgent as SkillEntry],
+ config: {
+ skills: {
+ allowBundled: ["coding-agent"],
+ entries: {
+ "coding-agent": { enabled: true },
+ },
+ },
+ },
+ eligibility,
+ }),
+ );
+ const enabledStatus = enabledReport.skills[0];
+ expect(enabledStatus?.eligible).toBe(true);
+ expect(enabledStatus?.missing.config).toEqual([]);
+ });
+
it("does not mark an overridden workspace skill as bundled by bundled name alone", async () => {
const bundledDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-bundled-"));
tempDirs.push(bundledDir);