Skip to content

beta.skills.list hard-capped at 100 items with broken pagination (has_more always false, no cursor) #1391

@RahulBalakavi

Description

@RahulBalakavi

Bug: beta.skills.list is hard-capped at 100 items with broken pagination

Summary

client.beta.skills.list(...) returns at most 100 skills regardless of the limit value, and the response always sets has_more: false with no usable cursor (next_page / after_id). On accounts with >100 skills, everything past the first page is unreachable. This causes silent data loss in any workflow that enumerates skills, and produces false "duplicate display_title" 400 errors at create time when the conflict happens to live in the invisible tail.

Environment

  • SDK: anthropic-python (latest as of 2026-04-14)
  • API: beta.skills.list
  • Account under test: >100 custom skills

Repro

import asyncio
from anthropic import AsyncAnthropic

async def main():
    c = AsyncAnthropic()
    for limit in (10, 50, 100, 500, 1000):
        items = []
        page = await c.beta.skills.list(limit=limit)
        async for item in page:
            items.append(item)
        print(f"limit={limit}: got {len(items)} items")

asyncio.run(main())

Actual output (with an account known to hold ~120 skills):

limit=10:   100 items
limit=50:   100 items
limit=100:  100 items
limit=500:  100 items
limit=1000: 100 items

The SDK's auto-paginator terminates after the first page because the response payload has has_more: false and no next_page / after_id, so it cannot request more. The limit parameter appears to be clamped server-side to 100 regardless of requested value.

Response shape (abbreviated)

{
  "data": [ /* exactly 100 items */ ],
  "has_more": false,
  "first_id": "skill_...",
  "last_id": "skill_..."
}

Passing after_id=<last_id> does not work to manually paginate either — the SDK exposes no such parameter, and direct HTTP calls we tried return the same first-100 window.

Impact on real code

We provision ~120 skills per environment via a script that calls skills.list() to reconcile against desired state. When the account exceeds 100 skills:

  1. The pre-fetch misses some existing skills.
  2. skills.create(display_title=X) returns a 400 "duplicate display_title" for skill X, even though X wasn't in the list() output.
  3. We can't look up X's skill_id to delete it, because we can't see it.

Our workaround (https://github.com/A79-ai/agentapp/pull/7846) catches the 400, deletes one of the visible scoped skills to shrink the total below 100, re-lists to reveal the conflict, deletes it, then retries the create. It works but is fragile and wastes a delete each cycle.

Ask

  1. Fix the has_more / cursor contract so skills.list actually paginates. Any of:
    • Return has_more: true + a working next_page or after_id cursor when the page is full.
    • Accept after_id=<last_id> as a request param and honor it.
  2. Or, if pagination is intentionally deferred, raise the 100-item hard cap substantially (e.g. 1000) and document the limit.

Related

Both issues compound: we can't enumerate all skills, and even for the ones we can see we can't tell which files back them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions