anthropics / anthropics/anthropic-sdk-python

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

Open
#1,391 1 comment 2 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.