UAT: SkillDefinition._validate_writes_consistency() only checks inline tools, misses write-capable tool_refs — read_only=True can be falsely reported #4812

Open
opened 2026-04-08 19:34:38 +00:00 by HAL9000 · 1 comment
Owner

Bug Report

Feature Area: Skill System — writes/read_only capability metadata validation
Severity: Medium — incorrect capability metadata can bypass write-gating in ToolRuntime
Found by: UAT tester instance uat-worker-tool-skill-system
Spec reference: docs/specification.md §Skill System — Writes / Read-Only Gating


What Was Tested

Code-level analysis of src/cleveragents/skills/protocol.pySkillDefinition._validate_writes_consistency() validator.

Expected Behavior (from spec)

The spec states that SkillMetadata.read_only=True means the skill is entirely read-only — no tool in the skill can write. The _validate_writes_consistency validator should enforce this invariant for ALL tool types in the resolved set (named tool_refs, inline tools, MCP tools, Agent Skills).

Actual Behavior

The _validate_writes_consistency validator in SkillDefinition only checks inline tools for write capability:

# src/cleveragents/skills/protocol.py
@model_validator(mode="after")
def _validate_writes_consistency(self) -> SkillDefinition:
    write_inline_count = 0
    for entry in self.resolved_tools:
        if (
            entry.is_inline                           # ← Only checks inline tools
            and entry.inline_tool is not None
            and entry.inline_tool.capability is not None
            and entry.inline_tool.capability.writes
        ):
            write_inline_count += 1

    if self.metadata.read_only and write_inline_count > 0:
        raise ValueError(...)

This means a SkillDefinition with metadata.read_only=True can be created even when it contains named tool_refs (from the Tool Registry) that have writes=True. The validator only catches write-capable inline tools.

Example of the Gap

# A skill that references a write-capable named tool
skill_def = SkillDefinition(
    skill=skill_with_write_tool_ref,  # tool_refs includes "builtin/write_file" (writes=True)
    resolved_tools=[
        ResolvedToolEntry(name="builtin/write_file", is_inline=False, ...)
    ],
    metadata=SkillMetadata(
        name="local/my-skill",
        description="...",
        read_only=True,   # ← Claims to be read-only
        writes=False,     # ← Claims no writes
    ),
)
# This passes validation! The validator doesn't check tool_refs.

Impact

A skill can be registered with read_only=True metadata but actually contain write-capable tool_refs. When the ToolRuntime enforces capability constraints, it reads SkillMetadata.read_only and writes flags. If these are incorrect, write-capable tools may be invoked in read-only plan contexts without triggering ToolAccessDeniedError.

Code Location

src/cleveragents/skills/protocol.pySkillDefinition._validate_writes_consistency() validator.

Fix

Extend the validator to also check non-inline resolved tool entries. For tool_refs, the capability information should come from the SkillCapabilityS ummary (already computed in SkillMetadata.from_skill()). The validator should compare metadata.read_only against metadata.capability_summary.write_tools > 0 or metadata.capability_summary.has_side_effects.


Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: uat-tester

## Bug Report **Feature Area:** Skill System — writes/read_only capability metadata validation **Severity:** Medium — incorrect capability metadata can bypass write-gating in ToolRuntime **Found by:** UAT tester instance `uat-worker-tool-skill-system` **Spec reference:** `docs/specification.md` §Skill System — Writes / Read-Only Gating --- ### What Was Tested Code-level analysis of `src/cleveragents/skills/protocol.py` — `SkillDefinition._validate_writes_consistency()` validator. ### Expected Behavior (from spec) The spec states that `SkillMetadata.read_only=True` means the skill is entirely read-only — no tool in the skill can write. The `_validate_writes_consistency` validator should enforce this invariant for ALL tool types in the resolved set (named tool_refs, inline tools, MCP tools, Agent Skills). ### Actual Behavior The `_validate_writes_consistency` validator in `SkillDefinition` **only checks inline tools** for write capability: ```python # src/cleveragents/skills/protocol.py @model_validator(mode="after") def _validate_writes_consistency(self) -> SkillDefinition: write_inline_count = 0 for entry in self.resolved_tools: if ( entry.is_inline # ← Only checks inline tools and entry.inline_tool is not None and entry.inline_tool.capability is not None and entry.inline_tool.capability.writes ): write_inline_count += 1 if self.metadata.read_only and write_inline_count > 0: raise ValueError(...) ``` This means a `SkillDefinition` with `metadata.read_only=True` can be created even when it contains **named tool_refs** (from the Tool Registry) that have `writes=True`. The validator only catches write-capable inline tools. ### Example of the Gap ```python # A skill that references a write-capable named tool skill_def = SkillDefinition( skill=skill_with_write_tool_ref, # tool_refs includes "builtin/write_file" (writes=True) resolved_tools=[ ResolvedToolEntry(name="builtin/write_file", is_inline=False, ...) ], metadata=SkillMetadata( name="local/my-skill", description="...", read_only=True, # ← Claims to be read-only writes=False, # ← Claims no writes ), ) # This passes validation! The validator doesn't check tool_refs. ``` ### Impact A skill can be registered with `read_only=True` metadata but actually contain write-capable tool_refs. When the ToolRuntime enforces capability constraints, it reads `SkillMetadata.read_only` and `writes` flags. If these are incorrect, write-capable tools may be invoked in read-only plan contexts without triggering `ToolAccessDeniedError`. ### Code Location `src/cleveragents/skills/protocol.py` — `SkillDefinition._validate_writes_consistency()` validator. ### Fix Extend the validator to also check non-inline resolved tool entries. For tool_refs, the capability information should come from the `SkillCapabilityS ummary` (already computed in `SkillMetadata.from_skill()`). The validator should compare `metadata.read_only` against `metadata.capability_summary.write_tools > 0 or metadata.capability_summary.has_side_effects`. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: uat-tester
Author
Owner

Issue triaged by project owner:

  • State: Verified
  • Priority: Medium — spec compliance bug identified by UAT testing
  • Story Points: 3 (M) — targeted fix to align implementation with spec
  • MoSCoW: Must Have — spec compliance is required for correct system behavior

Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner

Issue triaged by project owner: - **State**: Verified - **Priority**: Medium — spec compliance bug identified by UAT testing - **Story Points**: 3 (M) — targeted fix to align implementation with spec - **MoSCoW**: Must Have — spec compliance is required for correct system behavior --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner
HAL9000 added this to the v3.5.0 milestone 2026-04-09 03:03:36 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#4812
No description provided.