Repository navigation
Conversation
A confirmed set of public GET list routes never bind page_size/page_token
server-side (the API-gateway route lacks a query: block, so the generated
decoder reads neither param from the URL): /api/v1/automations,
/api/v1/automation_executions, /api/v1/functions, and
/api/v1/functions/{id}/commits among them. The server always answers page
1, resealing a fresh-looking nextPageToken on every call, so following the
cursor returns page 1 forever instead of reaching the rest of a large
result set. Live on a test tenant: with 150 automations, GET /api/v1/automations
returned the same 54 rows on every call; automation_executions returned
the same 30 of 34. c1i's automations list and automations executions list
both looped forever; functions list and functions commits were exposed to
the same defect without any guard at all.
This is a server defect, reported separately. There is no client-side
workaround that produces a complete result (no search/* equivalent is
substituted here), so the fix is to detect the stuck cursor and fail
loudly instead of hanging or silently returning an incomplete page at
exit 0:
- paginationCursorGuard (cmd/flags.go, from prior work) now backs
automations list, automations executions list, functions list, and
functions commits (and functions usage, which also reads
/api/v1/automations). It flags a server that sends the same page_token
twice, or a non-empty page whose every item id was already emitted
under an earlier (possibly different-looking) token.
- A tripped guard stops auto-pagination, keeps every row already printed
(no duplicates, no re-fetching), prints a stderr warning naming the
route and saying results are INCOMPLETE, and now returns a new
stuckPaginationCursorError so the process exits 6 ("C1 failed") instead
of silently exiting 0.
- `c1i api --paginate` (the generic escape hatch, cmd/api.go) gets the
same duplicate-page detection, not just its old same-token-twice check:
it compares each page's items by "id" when present, falling back to an
item's own canonical JSON when it has none, since `api` can target any
endpoint and can't assume an id field exists.
- cmd/errors.go, CLAUDE.md, .claude/commands/c1i.md, cmd/agents.md (the
`c1i docs agents` source), and README.md document the new exit-6 cause
and the stuckPaginationCursorError type.
Tests: cmd/list_pagination_test.go's shared table gains a
stuckCursorRoute field and TestListCommandsStuckCursorReturnsExitServer,
covering automations executions list, functions list, functions commits,
and functions usage against a server that reseals a new token over an
unchanged page; cmd/automations_list_pagination_test.go's three existing
stuck-cursor cases (same token, changing token, repeated empty page) are
updated to assert exit 6 instead of silent exit 0;
cmd/api_test.go gains three cases for `api --paginate` (same token twice,
changing token same rows, and the no-"id" canonical-JSON fallback). All
were confirmed to fail against the pre-fix code (an unguarded command
spins past the test's request-count ceiling; `api --paginate` returns the
old generic exit 1) and pass after.
Verified live on a test tenant (read-only except ~100 disabled draft
automations created and deleted for the reproduction, per the test
instructions): with 102 automations, automations list stops at 56 rows,
warns, and exits 6; automations executions list stops at 30 of 34 (cf.
POST /api/v1/search/automation_executions, which returns all 34); `api
--paginate --path /api/v1/automations` shows the same behavior generically.
Only the tenant's 2 real automations remain afterward.
cvkramer
force-pushed
the
fix/broken-server-pagination
branch
from
November 4, 2026 22:16
028f94d to
1d823b1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A confirmed set of public GET list routes never bind
page_size/page_tokenserver-side (the API-gateway route is missing a
query:block, so thegenerated decoder never reads either param from the URL). The server always
answers page 1 and reseals a fresh-looking
nextPageTokenon every call, sofollowing the cursor returns page 1 forever instead of reaching the rest of a
large result set:
/api/v1/automations/api/v1/automation_executions/api/v1/functions/api/v1/functions/{id}/commits/api/v1/apps/{app_id}/entitlement_configuration_rules,/api/v1/apps/{app_id}/entitlements/{app_entitlement_id}/automation/exclusions,/api/v1/decoys,/api/v1/functions/{id}/invocations,/api/v1/iam/model_api_keys,/api/v1/iam/personal_clients,/api/v1/iam/personal_devices/{id}/clients,/api/v1/iam/tunnel/bridges,/api/v1/llm-gateway/keys,/api/v1/role-mining/{suggestions,runs,custom-analysis},/api/v1/service_principals/{id}/trusts,/api/v1/settings/ai-governance/history,/api/v1/step-up/providers,/api/v1/workload_federation/providers)Live on a test tenant: with 150 automations,
GET /api/v1/automationsreturnedthe same 54 rows on every call;
GET /api/v1/automation_executionsreturnedthe same 30 of 34.
c1i automations listandc1i automations executions listboth looped forever on the shifting-but-non-advancing cursor.
functions listand
functions commitswere exposed to the same defect with no guard at all.This is a server-side bug, being reported separately. There is no
client-side fix that produces a complete result for these routes, so this PR makes c1i detect the stuck
cursor and fail loudly instead of hanging or silently returning an incomplete
page at exit 0.
What changed
paginationCursorGuard(cmd/flags.go, from prior work on this branch) nowbacks
automations list,automations executions list,functions list,and
functions commits(andfunctions usage, which also reads/api/v1/automations). It flags a server that sends the samepage_tokentwice, or a non-empty page whose every item id was already emitted under an
earlier (possibly different-looking) token.
duplicates, no re-fetching the stuck page), prints a stderr warning naming
the route and saying results are INCOMPLETE, and now returns a new
stuckPaginationCursorErrorso the process exits 6 ("C1 failed")instead of silently exiting 0.
c1i api --paginate(the generic escape hatch) gets the sameduplicate-page detection, not just its previous same-token-twice check: it
compares each page's items by
"id"when present, falling back to theitem's own canonical JSON when it has none, since
apican target anyendpoint and can't assume an id field exists.
cmd/errors.go,CLAUDE.md,.claude/commands/c1i.md,cmd/agents.md(the
c1i docs agentssource), andREADME.mddocument the new exit-6cause and the
stuckPaginationCursorErrortype.Per-command / per-route disposition
GET /api/v1/automationsautomations list,functions usageGET /api/v1/automation_executionsautomations executions listGET /api/v1/functionsfunctions listGET /api/v1/functions/{id}/commitsfunctions commitsapi --paginate/api/v1/service_principals(not confirmed broken)service_principals listTests
cmd/list_pagination_test.go: the sharedlistPaginationCasetable gains astuckCursorRoutefield andTestListCommandsStuckCursorReturnsExitServer,covering
automations executions list,functions list,functions commits, andfunctions usageagainst a server that reseals anew token over an unchanged page.
cmd/automations_list_pagination_test.go: the three existing stuck-cursorcases (same token, changing token, repeated empty page) are updated to
assert exit 6 instead of silent exit 0.
cmd/api_test.go: three new cases forapi --paginate— same token twice,changing token/same rows, and the no-
"id"canonical-JSON fallback.code (an unguarded command spins past the test's request-count ceiling
and the mock server exhausts its fixture;
api --paginatereturns the oldgeneric exit 1) and pass after the fix.
go test -count=1 ./cmd/...: pass.go vet ./...: clean.gofmt -l .:clean.
golangci-lint run --timeout=3m(v2.15.0, matching CI): 0 issues.go mod tidy: no diff.go build ./...: clean.go test -count=1 ./...has one unrelated pre-existing failure,TestStoreSweepLeavesCredentialsDirininternal/tokensource, confirmed tofail identically on a clean
origin/maincheckout on this machine (amacOS-specific tmp-dir/
Application Supportpath assumption, unrelated tothis change).
Live smoke (read-only against a test tenant, except the documented test data)
Created 100 disabled, draft "inert" automations (
zz-pagetest 001..100) viac1i api --path /api/v1/automations --body '{...isDraft/enabled:false...}'to push the tenant past the ~54-row page-1 cap, per the reproduction recipe.
All 100 inert automations were deleted afterward
(
DELETE /api/v1/automations/{id}per id);automations listnow showsexactly the tenant's 2 real automations ("Onboarding - birthright access &
welcome", "Mover + Leaver - department change & offboarding"), confirming
cleanup.
Uncertainties
today, so there's nothing in this repo to guard — flagged for awareness,
not fixed here.
gosec/govulncheckweren't run locally (not installed in thisenvironment); the changes here are pure JSON/string handling with no new
file, network, or process operations, so no new findings are expected, but
CI will run both.
this PR intentionally contains no server-side change.