HeyPuter/puter · error · HttpError
bad_request
bad_request
Error message
Missing or invalid `name` query param
What it means
AppDriver.select enforces a single pagination mode per request. If args.cursor decodes to a payload AND args.offset is also defined, it throws 400 bad_request. Mixing cursor- and offset-based pagination is ambiguous (cursor already encodes position), so the driver refuses rather than guessing which wins. See doc/pagination.md for the one convention.
Source
Thrown at src/backend/controllers/apps/AppController.js:175
router.get(
'/apps/nameAvailable',
{
subdomain: 'api',
requireAuth: true,
// Answers "does this name exist?" for any name, so it is a
// name-enumeration oracle however cheap it is to serve.
// Mirrors the `isNameAvailable` budget on AppDriver.
rateLimit: {
scope: 'app-name-available',
limit: 60,
window: 60_000,
key: 'user',
},
},
async (req, res) => {
const name = req.query?.name;
if (!name || typeof name !== 'string') {
throw new HttpError(
400,
'Missing or invalid `name` query param',
{ legacyCode: 'bad_request' },
);
}
const available = await this.appDriver.isNameAvailable(name);
res.json({ name, available });
},
);
// POST /rao — record a recent app open. When an app-under-user
// actor calls this, the app id is already on the token — clients
// don't re-send it in the body. Fall back to `actor.app.uid`
// before 400-ing for a missing body field.
//
// Authorization: only two callers are trusted to report opens —
// 1. a root user actor (plain session, no `.app` and no access
// token), e.g. the GUI launching apps on behalf of the user;View on GitHub (pinned to 908ec23eda)
Solutions
- Send exactly one of cursor or offset — never both.
- For 'next page', pass only the cursor returned by the previous response and drop offset.
- Reset offset to undefined when adopting cursor pagination in the client.
- Validate the params object before submit: assert !(cursor && offset != null).
Example fix
// before
await puter.apps.list({ cursor: prevCursor, offset: 0, limit: 50 });
// after — pick one pagination mode
await puter.apps.list({ cursor: prevCursor, limit: 50 });
// or
await puter.apps.list({ offset: 100, limit: 50 }); Defensive patterns
Strategy: validation
Validate before calling
function assertOnePager(args) {
if (args.cursor != null && args.offset != null) {
throw new Error('cursor and offset cannot be combined');
}
}
assertOnePager(params); Prevention
- Pick one pagination mode (cursor OR offset) per list flow and reset the other to undefined.
- For 'next page', forward only the server-returned cursor and drop any default offset.
- See doc/pagination.md for the single convention used across list APIs.
When it happens
Trigger: Calling puter.apps.list() / the driver select() with both `cursor` (from a previous page) and `offset` set in the same request — e.g. a UI that held an offset default while also forwarding the server's cursor.
Common situations: Client merged a cursor-based 'next page' flow with a legacy offset-based default; query-param builder always includes offset=0; refactor left an offset field populated alongside the cursor.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/d7b6073ce0dd0a7c.
Report an issue: GitHub.