Skip to content

Skills: enabled: true overrides requires.bins check#32789

Open
haosenwang1018 wants to merge 4 commits intoopenclaw:mainfrom
haosenwang1018:fix/skill-enabled-override-bins
Open

Skills: enabled: true overrides requires.bins check#32789
haosenwang1018 wants to merge 4 commits intoopenclaw:mainfrom
haosenwang1018:fix/skill-enabled-override-bins

Conversation

@haosenwang1018
Copy link
Copy Markdown
Contributor

Summary

  • Problem: skills.entries.<key>.enabled: true does not force-include a skill. It falls through to evaluateRuntimeEligibility(), which rejects the skill when requires.bins lists binaries not found on the system.
  • Why it matters: Users expect enabled: true to be the symmetric counterpart of enabled: false — an unconditional override that forces the skill on regardless of runtime checks.
  • What changed: Added an early return for enabled === true in shouldIncludeSkill(), symmetric with the existing enabled === false path, so the skill is included without evaluating runtime requirements.
  • What did NOT change (scope boundary): The enabled === false path, the bundled-allowlist check, and all evaluateRuntimeEligibility logic remain untouched.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

Setting skills.entries.<key>.enabled: true in config now unconditionally includes the skill, bypassing requires.bins, requires.env, and other runtime eligibility checks.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Steps

  1. Configure a skill entry with enabled: true and a requires.bins dependency that is not installed.
  2. Before the fix: the skill is excluded (bug).
  3. After the fix: the skill is included (correct).

Expected

  • Skill is included when enabled: true, regardless of requires.bins.

Actual

  • Before fix: Skill was excluded because evaluateRuntimeEligibility rejected it.

Evidence

  • Failing test/log before + passing after
  • New test file src/agents/skills/config.test.ts covers:
    • enabled: false → excluded
    • enabled: true with unmet requires.bins → included (the bug scenario)
    • enabled: true without requires → included
    • No enabled set + unmet bins → excluded (falls through to runtime check)
    • No skill config at all + unmet bins → excluded

Human Verification (required)

  • Verified scenarios: All 5 new tests pass; all 23 tests in src/agents/skills/ pass.
  • Edge cases checked: enabled: undefined (no config entry) still falls through to runtime eligibility.
  • What you did not verify: End-to-end with a real skill directory and missing binary.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert the 3-line addition in shouldIncludeSkill().
  • Files/config to restore: src/agents/skills/config.ts
  • Known bad symptoms reviewers should watch for: A skill that should be excluded by requires.bins being incorrectly included when no explicit enabled is set.

Risks and Mitigations

  • Risk: A user who previously set enabled: true as a no-op annotation may now get a skill that was previously filtered out by runtime checks.
    • Mitigation: This is the documented/expected behavior of enabled: true; users who set it explicitly want the skill included.

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: M labels Mar 3, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 3, 2026

Greptile Summary

This PR bundles three independent fixes: (1) the headline fix making skills.entries.<key>.enabled: true an unconditional force-include in shouldIncludeSkill, symmetric with the existing enabled: false path; (2) a compaction-safeguard change that replaces { cancel: true } with a minimal compaction result when there are no real conversation messages, preventing context from staying oversized; and (3) Matrix auto-threading support, mirroring the Telegram pattern, plus a graceful sendMedia-absent degradation in the plugin handler.

Key findings:

  • The enabled: true early return is placed before isBundledSkillAllowed, meaning it also silently bypasses the bundled-skill allowlist — a side-effect not mentioned in the scope boundary and not covered by any test. Whether intentional, this invariant should be tested and documented.
  • resolveMatrixAutoThreadId normalises room IDs with a full toLowerCase(), but Matrix room-ID localparts are case-sensitive per spec, creating a potential false-positive thread injection if two rooms differ only in localpart case.
  • The compaction and deliver changes are clean and well-tested.

Confidence Score: 3/5

  • Mostly safe to merge, but the undocumented bundled-allowlist bypass and the Matrix room-ID case-sensitivity issue should be resolved or explicitly accepted before landing.
  • The core skills fix is straightforward and well-tested for the stated scenarios, but the early return's placement before isBundledSkillAllowed introduces a silent scope creep that has no test coverage. The Matrix threading addition has a real-world-low-risk but spec-incorrect case-insensitive comparison. The compaction and deliver changes are solid.
  • src/agents/skills/config.ts (bundled-allowlist bypass) and src/infra/outbound/message-action-params.ts (Matrix room-ID case sensitivity).

Last reviewed commit: d4ce4fa

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +83 to +85
if (skillConfig?.enabled === true) {
return true;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enabled: true silently bypasses isBundledSkillAllowed

The new early return at line 83–85 is placed before the isBundledSkillAllowed check (line 86), so setting enabled: true on a bundled skill now unconditionally includes it even when skills.allowBundled is configured to exclude that skill. The PR description says "the bundled-allowlist check … remain untouched," but it is now skippable for any explicitly-enabled skill.

If this is intentional (enabled: true means "force-include regardless of every guard"), it should be documented and tested explicitly. For example, a test where entry.skill.source === "openclaw-bundled", allowBundled does not list the skill, and enabled: true still returns true would make the invariant explicit. As written, the bundled-allowlist can be silently overridden with no test coverage for that path.

If the intent is narrower—only bypass runtime binary/env checks—then the early return should be moved to after the isBundledSkillAllowed guard:

Suggested change
if (skillConfig?.enabled === true) {
return true;
}
if (skillConfig?.enabled === false) {
return false;
}
if (!isBundledSkillAllowed(entry, allowBundled)) {
return false;
}
if (skillConfig?.enabled === true) {
return true;
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/skills/config.ts
Line: 83-85

Comment:
**`enabled: true` silently bypasses `isBundledSkillAllowed`**

The new early return at line 83–85 is placed *before* the `isBundledSkillAllowed` check (line 86), so setting `enabled: true` on a bundled skill now unconditionally includes it even when `skills.allowBundled` is configured to exclude that skill. The PR description says "the bundled-allowlist check … remain untouched," but it is now skippable for any explicitly-enabled skill.

If this is intentional (`enabled: true` means "force-include regardless of every guard"), it should be documented and tested explicitly. For example, a test where `entry.skill.source === "openclaw-bundled"`, `allowBundled` does **not** list the skill, and `enabled: true` still returns `true` would make the invariant explicit. As written, the bundled-allowlist can be silently overridden with no test coverage for that path.

If the intent is narrower—only bypass runtime binary/env checks—then the early return should be moved to *after* the `isBundledSkillAllowed` guard:

```suggestion
  if (skillConfig?.enabled === false) {
    return false;
  }
  if (!isBundledSkillAllowed(entry, allowBundled)) {
    return false;
  }
  if (skillConfig?.enabled === true) {
    return true;
  }
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +100 to +103
}): string | undefined {
const context = params.toolContext;
if (!context?.currentThreadTs || !context.currentChannelId) {
return undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Case-insensitive comparison of case-sensitive Matrix room IDs

Matrix room IDs have the form !localpart:server. The server name is a DNS hostname (case-insensitive), but the localpart is an opaque, server-generated string that is case-sensitive per the Matrix specification. Applying .toLowerCase() to the full room ID (including the localpart) can cause !RoomA:example.com and !rooma:example.com to match even though they are different rooms.

Consider only lower-casing the server portion (after the last :) while preserving the localpart's original case for comparison:

function normalizeMatrixRoomId(raw: string): string {
  const stripped = stripMatrixRoomPrefix(raw.trim());
  const colonIdx = stripped.lastIndexOf(":");
  if (colonIdx === -1) return stripped;
  return stripped.slice(0, colonIdx) + stripped.slice(colonIdx).toLowerCase();
}

Then replace the current comparison:

if (normalizeMatrixRoomId(params.to) !== normalizeMatrixRoomId(context.currentChannelId)) {
  return undefined;
}

The test "matches case-insensitively" passes !ABC123:Matrix.Org vs !abc123:matrix.org—this passes today but would mask a real mismatch if those were actually two distinct rooms.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/outbound/message-action-params.ts
Line: 100-103

Comment:
**Case-insensitive comparison of case-sensitive Matrix room IDs**

Matrix room IDs have the form `!localpart:server`. The server name is a DNS hostname (case-insensitive), but the `localpart` is an opaque, server-generated string that is **case-sensitive** per the Matrix specification. Applying `.toLowerCase()` to the full room ID (including the localpart) can cause `!RoomA:example.com` and `!rooma:example.com` to match even though they are different rooms.

Consider only lower-casing the server portion (after the last `:`) while preserving the localpart's original case for comparison:

```typescript
function normalizeMatrixRoomId(raw: string): string {
  const stripped = stripMatrixRoomPrefix(raw.trim());
  const colonIdx = stripped.lastIndexOf(":");
  if (colonIdx === -1) return stripped;
  return stripped.slice(0, colonIdx) + stripped.slice(colonIdx).toLowerCase();
}
```

Then replace the current comparison:
```typescript
if (normalizeMatrixRoomId(params.to) !== normalizeMatrixRoomId(context.currentChannelId)) {
  return undefined;
}
```

The test `"matches case-insensitively"` passes `!ABC123:Matrix.Org` vs `!abc123:matrix.org`—this passes today but would mask a real mismatch if those were actually two distinct rooms.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4ce4fa7fa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +83 to +84
if (skillConfig?.enabled === true) {
return true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep bundled allowlist enforcement for force-enabled skills

Returning early on enabled === true now bypasses isBundledSkillAllowed, so a bundled skill blocked by skills.allowBundled becomes eligible as soon as skills.entries.<key>.enabled: true is set. That contradicts the documented allowlist contract and changes behavior beyond the commit’s stated scope (runtime requirements only), so users relying on allowBundled as a hard gate can unintentionally expose blocked bundled skills.

Useful? React with 👍 / 👎.

Comment on lines +154 to +158
((ctx: ChannelOutboundContext) => {
log.warn("sendMedia not implemented; degrading to sendText", {
channel: params.channel,
});
return sendText({ ...ctx, mediaUrl: undefined });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent empty text sends in sendMedia fallback path

This fallback converts every sendMedia call into sendText, but delivery still invokes sendMedia once per media URL and uses an empty caption after the first attachment. For payloads with multiple media URLs (or media-only payloads), that produces extra blank text messages (or channel errors if empty text is rejected), so the degradation path can spam/fail instead of cleanly downgrading media delivery.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: skills.entries.<key>.enabled: true does not override requires.bins check

1 participant