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:
- The pre-fetch misses some existing skills.
skills.create(display_title=X) returns a 400 "duplicate display_title" for skill X, even though X wasn't in the list() output.
- 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
- 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.
- 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.
Bug:
beta.skills.listis hard-capped at 100 items with broken paginationSummary
client.beta.skills.list(...)returns at most 100 skills regardless of thelimitvalue, and the response always setshas_more: falsewith 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
anthropic-python(latest as of 2026-04-14)beta.skills.listRepro
Actual output (with an account known to hold ~120 skills):
The SDK's auto-paginator terminates after the first page because the response payload has
has_more: falseand nonext_page/after_id, so it cannot request more. Thelimitparameter 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:skills.create(display_title=X)returns a 400 "duplicate display_title" for skill X, even though X wasn't in thelist()output.deleteit, 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
has_more/ cursor contract soskills.listactually paginates. Any of:has_more: true+ a workingnext_pageorafter_idcursor when the page is full.after_id=<last_id>as a request param and honor it.Related
skills.versions.retrievenot exposing backingfile_ids (issue Cannot GC orphan Files API files — skill→file linkage not exposed on skills.versions.retrieve #1390).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.