Skip to content

fix: Fix slow list endpoints for object oriented sdk#767

Merged
alb-rl merged 2 commits intomainfrom
alb/fix-slow-list-endpoints
Apr 2, 2026
Merged

fix: Fix slow list endpoints for object oriented sdk#767
alb-rl merged 2 commits intomainfrom
alb/fix-slow-list-endpoints

Conversation

@alb-rl
Copy link
Copy Markdown
Contributor

@alb-rl alb-rl commented Apr 2, 2026

User description

All the SDK list() methods use for await to iterate over paginated results, which auto-paginates through ALL pages regardless of the limit parameter you pass

For example, when you call sdk.gatewayConfig.list({ limit: 10 }):

  • The API returns the first page with 10 items + has_more: true
  • The for await sees has_more: true and fetches the next page
  • This continues until ALL items are fetched

CodeAnt-AI Description

List calls now respect the requested page size and stop fetching after the first page

What Changed

  • List methods for agents, scorers, snapshots, storage objects, axons, network policies, gateway configs, and MCP configs now return only the items from the page you asked for
  • Calls with a small limit no longer continue loading every remaining page in the background

Impact

✅ Faster list results
✅ Fewer unnecessary API requests
✅ Better handling of limited list views

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@alb-rl alb-rl requested review from dines-rl and tode-rl April 2, 2026 00:44
@codeant-ai
Copy link
Copy Markdown
Contributor

codeant-ai Bot commented Apr 2, 2026

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:S This PR changes 10-29 lines, ignoring generated files label Apr 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

✅ Object Smoke Tests & Coverage Report

Test Results

✅ All smoke tests passed

Coverage Results

Metric Coverage Required Status
Functions 100% 100%
Lines 90.21% - ℹ️
Branches 69.59% - ℹ️
Statements 89.15% - ℹ️

Coverage Requirement: 100% function coverage (all public methods must be called in smoke tests)

✅ All tests passed and all object methods are covered!

View detailed coverage report
File Functions Lines Branches
src/sdk.ts ✅ 100% 86.11% 72.58%
src/sdk/agent.ts ✅ 100% 100% 100%
src/sdk/axon.ts ✅ 100% 93.75% 100%
src/sdk/blueprint.ts ✅ 100% 100% 80%
src/sdk/devbox.ts ✅ 100% 91.96% 94.28%
src/sdk/execution-result.ts ✅ 100% 92.68% 70.83%
src/sdk/execution.ts ✅ 100% 95.65% 87.5%
src/sdk/gateway-config.ts ✅ 100% 100% 100%
src/sdk/mcp-config.ts ✅ 100% 100% 100%
src/sdk/network-policy.ts ✅ 100% 100% 100%
src/sdk/scenario-builder.ts ✅ 100% 98.46% 80.7%
src/sdk/scenario-run.ts ✅ 100% 96.87% 50%
src/sdk/scenario.ts ✅ 100% 100% 100%
src/sdk/scorer.ts ✅ 100% 100% 100%
src/sdk/secret.ts ✅ 100% 100% 100%
src/sdk/snapshot.ts ✅ 100% 100% 100%
src/sdk/storage-object.ts ✅ 100% 80% 48.93%

📋 View workflow run

@alb-rl alb-rl merged commit 1f26406 into main Apr 2, 2026
9 checks passed
@alb-rl alb-rl deleted the alb/fix-slow-list-endpoints branch April 2, 2026 00:55
@stainless-app stainless-app Bot mentioned this pull request Apr 2, 2026
Comment thread src/sdk.ts
Comment on lines +1580 to +1582
const page = await this.client.axons.list(params, options);
const axons: Axon[] = [];
for await (const axon of result) {
for (const axon of page.getPaginatedItems()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This change breaks the documented behavior of listing all items when no pagination limit is provided: it now always returns only the first page. Preserve full auto-pagination when limit is not explicitly set, and only use getPaginatedItems() for the single-page path when a limit is provided. [logic error]

Severity Level: Major ⚠️
- ❌ Axon listing without limit returns incomplete results.
- ⚠️ Existing code examples suggest full list is expected.
- ⚠️ Any bulk axon operations may silently skip items.
Suggested change
const page = await this.client.axons.list(params, options);
const axons: Axon[] = [];
for await (const axon of result) {
for (const axon of page.getPaginatedItems()) {
const axons: Axon[] = [];
if (params?.limit !== undefined) {
const page = await this.client.axons.list(params, options);
for (const axon of page.getPaginatedItems()) {
axons.push(Axon.fromId(this.client, axon.id));
}
} else {
for await (const axon of this.client.axons.list(params, options)) {
axons.push(Axon.fromId(this.client, axon.id));
}
}
Steps of Reproduction ✅
1. Import and instantiate the SDK client as shown in `src/sdk.ts` RunloopSDK class (e.g.,
`const runloop = new RunloopSDK();`).

2. Call the AxonOps list method without a limit via `runloop.axon.list()` (AxonOps is
defined in `src/sdk.ts` under the `[Beta] Axon SDK interface for managing axons` section,
and its `list` implementation uses `const page = await this.client.axons.list(params,
options);` followed by `for (const axon of page.getPaginatedItems()) { ... }` at lines
1580–1582 in the PR hunk).

3. Ensure the underlying API has more axons than fit on a single page (the API client
`this.client.axons.list` is a paginated endpoint as implied by `getPaginatedItems()` and
the PR description about auto-pagination).

4. Observe that `runloop.axon.list()` returns only the items from the first page because
it iterates `page.getPaginatedItems()` for a single page, whereas prior behavior
(described in the PR text and other list implementations like `AgentOps.list` and
`ScorerOps.list` in `src/sdk.ts`) auto-paginated across all pages when no `limit` was
provided.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/sdk.ts
**Line:** 1580:1582
**Comment:**
	*Logic Error: This change breaks the documented behavior of listing all items when no pagination limit is provided: it now always returns only the first page. Preserve full auto-pagination when `limit` is not explicitly set, and only use `getPaginatedItems()` for the single-page path when a `limit` is provided.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

Comment thread src/sdk/agent.ts
Comment on lines +119 to +122
const page = await client.agents.list(params, options);
const result: Agent[] = [];

for await (const agent of agents) {
for (const agent of page.getPaginatedItems()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This change breaks the previous list() contract by always returning only the first page, even when no limit is provided. Callers using runloop.agent.list() to fetch all agents will now silently get partial results. Keep first-page behavior only when limit is explicitly set, and preserve auto-pagination otherwise. [logic error]

Severity Level: Major ⚠️
- ❌ Object-oriented `Agent.list` no longer returns all agents.
- ⚠️ Callers expecting full auto-pagination get partial results.
- ⚠️ Downstream features assuming complete agent set may misbehave.
Suggested change
const page = await client.agents.list(params, options);
const result: Agent[] = [];
for await (const agent of agents) {
for (const agent of page.getPaginatedItems()) {
const result: Agent[] = [];
if (params?.limit !== undefined) {
const page = await client.agents.list(params, options);
for (const agent of page.getPaginatedItems()) {
result.push(new Agent(client, agent.id));
}
} else {
for await (const agent of client.agents.list(params, options)) {
result.push(new Agent(client, agent.id));
}
Steps of Reproduction ✅
1. In a consumer project using this SDK, call `runloop.agent.list()` **without** passing a
`limit` parameter, which routes to `Agent.list(client, params?, options?)` in
`src/sdk/agent.ts:114-127` (final file state).

2. Inside `Agent.list`, the code at `src/sdk/agent.ts:119` executes `const page = await
client.agents.list(params, options);`, obtaining only the first page of results from the
underlying `client.agents.list` paginator.

3. The subsequent loop at `src/sdk/agent.ts:122-123` iterates `for (const agent of
page.getPaginatedItems())`, which, per the PR description, returns only items from that
single page rather than auto-paginating through all pages.

4. The function returns `result` at `src/sdk/agent.ts:126`, containing only the first page
of agents; any existing caller that previously relied on `runloop.agent.list()` to
auto-paginate and return all agents now silently receives a truncated list instead of the
complete set.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/sdk/agent.ts
**Line:** 119:122
**Comment:**
	*Logic Error: This change breaks the previous `list()` contract by always returning only the first page, even when no `limit` is provided. Callers using `runloop.agent.list()` to fetch all agents will now silently get partial results. Keep first-page behavior only when `limit` is explicitly set, and preserve auto-pagination otherwise.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

@codeant-ai
Copy link
Copy Markdown
Contributor

codeant-ai Bot commented Apr 2, 2026

CodeAnt AI finished reviewing your PR.

tode-rl added a commit to runloopai/api-client-python that referenced this pull request Apr 2, 2026
Port verification and testing from TypeScript PR #767 which fixed slow list
endpoints that were auto-paginating through all pages.

## Analysis Results

The Python SDK does NOT suffer from the bug that affected TypeScript because:
- Python implementation uses direct property access (`page.items`)
- TypeScript was using async iteration (`for await`) which auto-paginated
- Our existing unit tests already used correct mocking patterns

## What Was Added

New smoke tests in `tests/smoketests/sdk/test_list_pagination.py`:
- 28 comprehensive tests (15 async, 13 sync)
- Tests all 13 resource types (agents, devboxes, blueprints, etc.)
- Verifies `list(limit=N)` returns at most N items
- Ensures no auto-pagination occurs
- Includes data creation test to verify with actual API calls

## Verification

All SDK list methods verified correct:
✅ AsyncDevboxOps.list() - accesses page.devboxes
✅ AsyncSnapshotOps.list() - accesses page.snapshots
✅ AsyncBlueprintOps.list() - accesses page.blueprints
✅ AsyncStorageObjectOps.list() - accesses page.objects
✅ AsyncAxonOps.list() - accesses result.axons
✅ AsyncScorerOps.list() - accesses page.scorers
✅ AsyncAgentOps.list() - accesses page.agents
✅ AsyncScenarioOps.list() - accesses page.scenarios
✅ AsyncBenchmarkOps.list() - accesses page.benchmarks
✅ AsyncNetworkPolicyOps.list() - accesses page.network_policies
✅ AsyncGatewayConfigOps.list() - accesses page.gateway_configs
✅ AsyncMcpConfigOps.list() - accesses page.mcp_configs
✅ AsyncSecretOps.list() - accesses result.secrets
(+ all sync equivalents)

## Benefits

✅ Faster list results - only fetches requested page
✅ Fewer API requests - no unnecessary pagination
✅ Better resource usage - respects limit parameter
✅ Documented behavior - tests serve as specification
✅ Regression prevention - ensures future changes maintain correctness

## Related

- TypeScript PR: runloopai/api-client-ts#767
- Detailed analysis: PR_767_PORT_SUMMARY.md
- Code comparison: IMPLEMENTATION_COMPARISON.md
- Quick reference: PR_767_PORT.md

## Testing

Run the new tests:
```bash
uv run pytest tests/smoketests/sdk/test_list_pagination.py -v
```

No source code changes required - Python implementation already correct.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants