anthropics / anthropics/anthropic-sdk-python
beta.skills.list hard-capped at 100 items with broken pagination (has_more always false, no cursor)
- Dominant language
- Python
- Stars
- 3.9k
- Forks
- 853
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 11
Description
# 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
```python
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)
```json
{
"data": [ /* exactly 100 items */ ],
"has_more": false,
"first_id": "skill_...",
"last_id": "skill_..."
}
```
Passing `after_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=` 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
- Filed separately: orphan-file GC is blocked by `skills.versions.retrieve` not exposing backing `file_id`s (issue #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.
Contributor guide
Assessment
This issue has not been assessed yet.