Public: anyone can read this; the crew can change it.
`GET …/app/pulls` lists an adapter's recorded pull runs, newest first #10112
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/app/pulls` now returns an app's recorded pull runs as `{ items, next_cursor: null }`, newest first. `?limit=` takes a whole number from 1 to 100 (default 20); anything else gets a 422. Each item is `{ id, at, durationMs, state, error, written, skipped, failed, cursor, repeat }`. `error` holds the failure sentence and is null on a good run. `cursor` is null after a f…
shipped production is serving it deploy aa46b8b
GET /api/v1/repos/:owner/:name/app/pulls now returns an app's recorded pull runs as { items, next_cursor: null }, newest first. ?limit= takes a whole number from 1 to 100 (default 20); anything else gets a 422. Each item is { id, at, durationMs, state, error, written, skipped, failed, cursor, repeat }. error holds the failure sentence and is null on a good run. cursor is null after a failed run.
What to look at
- Read function:
pullRunLines(db, appId, limit)insrc/apps/pull.tsreads the rows through the existinglistPullRunsand returns plain data, so a page or MCP can reuse it. The work already in flight here (#10037, #10084) is untouched. - Route: it lives in a new small module,
src/http/api/app_pulls.ts, registered next toappRoutes. It uses the same gate asGET …/app: anyone who can read the repository sees the runs, including a guest on a public app. Anyone else, or a repository with no app, gets 404. There is deliberately no token-scope check: one would have turned a guest's 404 into a 401, so a stranger could tell the private app exists. - OpenAPI: the route is in
src/contract/openapi/routes/getApiV1ReposOwnerNameAppPulls.ts, with the limit bound taken from the same constant the handler uses. The index was regenerated withbun scripts/openapi-routes.ts. - Docs: the Status section of
docs/ADAPTERS.mdnow says the API reads the runs. - Test:
test/adapter-pulls-api.test.tsrecords a good and a failed run and reads them back newest first. It checks the limit and its 422s, and that the owner can read a private app's runs while another member and a guest get 404, as does a repository that doesn't exist.
Left out
- MCP: no MCP action or page reads the runs yet. The function is ready for one, and
test/mcp-size.test.tspasses unchanged. - Paging: the list doesn't page, because 100 rows is the cap.
- Plan on the issue: I didn't keep one, because the
keep_plantool isn't available in this session.
Checks run: bun scripts/gate.ts, bun x tsc --noEmit, and the covering tests, all passing: the new test, app-pull-now, adapter-card, apps-adapter, contract, openapi* (coverage, limit-bounds, routes, served), route-timing-coverage, unreached and mcp-size. scripts/fast.ts named 1,564 files because src/http/api.ts changed, so the full suite is left to the forge's check.
Try it: curl https://lab--<pull>--forge-trevato-apps.open-platform.sh/api/v1/repos/<owner>/<app>/app/pulls?limit=5
🤖 Generated with Claude Code
GET /api/v1/repos/:owner/:name/app/pulls now returns an app's recorded pull runs as { items, next_cursor: null }, newest first. ?limit= takes a whole number from 1 to 100 (default 20); anything else gets a 422. Each item is { id, at, durationMs, state, error, written, skipped, failed, cursor, repeat }. error holds the failure sentence and is null on a good run. cursor is null after a failed run.
What to look at
- Read function:
pullRunLines(db, appId, limit)insrc/apps/pull.tsreads the rows through the existinglistPullRunsand returns plain data, so a page or MCP can reuse it. The work already in flight here (#10037, #10084) is untouched. - Route: it lives in a new small module,
src/http/api/app_pulls.ts, registered next toappRoutes. It uses the same gate asGET …/app: anyone who can read the repository sees the runs, including a guest on a public app. Anyone else, or a repository with no app, gets 404. There is deliberately no token-scope check: one would have turned a guest's 404 into a 401, so a stranger could tell the private app exists. - OpenAPI: the route is in
src/contract/openapi/routes/getApiV1ReposOwnerNameAppPulls.ts, with the limit bound taken from the same constant the handler uses. The index was regenerated withbun scripts/openapi-routes.ts. - Docs: the Status section of
docs/ADAPTERS.mdnow says the API reads the runs. - Test:
test/adapter-pulls-api.test.tsrecords a good and a failed run and reads them back newest first. It checks the limit and its 422s, and that the owner can read a private app's runs while another member and a guest get 404, as does a repository that doesn't exist. - Gate fix: the first push failed the gate because no commit message said why the work goes now or how to try it. An empty follow-up commit adds the
Why now:andTry:lines, so no force push was needed.
Left out
- MCP: no MCP action or page reads the runs yet. The function is ready for one, and
test/mcp-size.test.tspasses unchanged. - Paging: the list doesn't page, because 100 rows is the cap.
- Plan on the issue: I didn't keep one, because the
keep_plantool isn't available in this session.
Checks run:
- First push: gate, type check and the covering tests all passed. The tests were the new one,
app-pull-now,adapter-card,apps-adapter,contract,openapi*(coverage, limit-bounds, routes, served),route-timing-coverage,unreachedandmcp-size. - After the gate fix:
FORGE_CHECK_BASE=0c3a3ea5 bun scripts/gate.tsandbun x tsc --noEmitpass, and so dotest/adapter-pulls-api.test.tsandtest/openapi-routes.test.ts. - Full suite:
scripts/fast.tsnamed 1,564 files becausesrc/http/api.tschanged, so the full suite is left to the forge's check.
Try it: curl https://lab--<pull>--forge-trevato-apps.open-platform.sh/api/v1/repos/<owner>/<app>/app/pulls?limit=5
🤖 Generated with Claude Code
Clean. The route gates on loadRepo + requireReader and answers 404 for a stranger, a guest on a private app and a repo with no app, so it does not reveal that a private app exists. limit is validated by a strict regex, with a 422 outside 1-100. The read function is plain data, the OpenAPI route is registered, and the tests cover order, limit, 404s and clamping. The error text is the length-capped reason the forge recorded, shown only to readers of the repo. I did not run the new tests. A request to the preview for a repo with no app returned 404 as expected. The issue asks for the API only, so there is no TUI screen and the docs change in ADAPTERS.md is what it asked for.
GET /api/v1/repos/:owner/:name/app/pullsnow returns an app's recorded pull runs as{ items, next_cursor: null }, newest first.?limit=takes a whole number from 1 to 100 (default 20); anything else gets a 422. Each item is{ id, at, durationMs, state, error, written, skipped, failed, cursor, repeat }.errorholds the failure sentence and is null on a good run.cursoris null after a failed run.What to look at
pullRunLines(db, appId, limit)insrc/apps/pull.tsreads the rows through the existinglistPullRunsand returns plain data, so a page or MCP can reuse it. The work already in flight here (#10037, #10084) is untouched.src/http/api/app_pulls.ts, registered next toappRoutes. It uses the same gate asGET …/app: anyone who can read the repository sees the runs, including a guest on a public app. Anyone else, or a repository with no app, gets 404. There is deliberately no token-scope check: one would have turned a guest's 404 into a 401, so a stranger could tell the private app exists.src/contract/openapi/routes/getApiV1ReposOwnerNameAppPulls.ts, with the limit bound taken from the same constant the handler uses. The index was regenerated withbun scripts/openapi-routes.ts.docs/ADAPTERS.mdnow says the API reads the runs.test/adapter-pulls-api.test.tsrecords a good and a failed run and reads them back newest first. It checks the limit and its 422s, and that the owner can read a private app's runs while another member and a guest get 404, as does a repository that doesn't exist.Left out
test/mcp-size.test.tspasses unchanged.keep_plantool isn't available in this session.Checks run:
bun scripts/gate.ts,bun x tsc --noEmit, and the covering tests, all passing: the new test,app-pull-now,adapter-card,apps-adapter,contract,openapi*(coverage, limit-bounds, routes, served),route-timing-coverage,unreachedandmcp-size.scripts/fast.tsnamed 1,564 files becausesrc/http/api.tschanged, so the full suite is left to the forge's check.Try it:
curl https://lab--<pull>--forge-trevato-apps.open-platform.sh/api/v1/repos/<owner>/<app>/app/pulls?limit=5🤖 Generated with Claude Code
Closes #10101