different-ai/openwork · error · ApiError

invalid_payload

invalid_payload

Error message

${field} is required

What it means

requireStringField is a helper in the session-groups routes that asserts a body field exists as a non-empty (after trim) string. Used for fields like "label" when renaming a group, it throws ApiError 400 code "invalid_payload" with the message "${field} is required" when the field is missing, not a string, or whitespace-only. The trimmed value is then used (e.g. sliced to 120 chars for labels).

Source

Thrown at apps/server/src/routes/session-groups.ts:51

    readJsonBody,
    ensureWritable,
    requireClientScope,
    resolveWorkspace,
    resolveWorkspaceWithoutBootstrap,
  } = options;
  const sessionGroupEvents = new SessionGroupEventStore();

  async function updateWorkspaceSessionGroups(
    workspaceId: string,
    updater: (current: SessionGroupState) => SessionGroupState,
  ) {
    return updateSessionGroupState(config, workspaceId, updater);
  }

  function requireStringField(body: Record<string, unknown>, field: string): string {
    const value = body[field];
    if (typeof value !== "string" || !value.trim()) {
      throw new ApiError(400, "invalid_payload", `${field} is required`);
    }
    return value.trim();
  }

  addRoute(routes, "GET", "/workspace/:id/session-groups", "client", async (ctx) => {
    const workspace = await resolveWorkspaceWithoutBootstrap(config, ctx.params.id);
    const result = await readSessionGroupState(config, workspace.id);
    return jsonResponse({ state: result.state, updatedAt: result.updatedAt });
  });

  addRoute(routes, "PUT", "/workspace/:id/session-groups", "client", async (ctx) => {
    ensureWritable(config);
    requireClientScope(ctx, "collaborator");
    const workspace = await resolveWorkspace(config, ctx.params.id);
    const body = await readJsonBody(ctx.request);
    const state = normalizeSessionGroupState(body.state);
    const result = await updateWorkspaceSessionGroups(workspace.id, () => state);
    sessionGroupEvents.record(workspace.id, "imported");

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Send the required field as a non-empty string, e.g. { "label": "My group" }.
  2. Validate on the client (non-empty after trim) before calling the endpoint; disable submit for blank input.
  3. Confirm the field name matches the API (label, not name or title).

Example fix

// before
await api.renameSessionGroup(id, groupId, { name: label ?? undefined });
// after
const trimmed = (label ?? "").trim();
if (!trimmed) throw new Error("Label is required");
await api.renameSessionGroup(id, groupId, { label: trimmed.slice(0, 120) });
Defensive patterns

Strategy: validation

Validate before calling

const label = typeof body.label === "string" ? body.label.trim() : "";
if (!label) throw new Error("label is required (non-empty string)");

Type guard

function isNonEmptyString(v) { return typeof v === "string" && v.trim().length > 0; }

Try / catch

try {
  await api.renameSessionGroup(wid, gid, { label });
} catch (e) {
  if (e.code === "invalid_payload" && /label is required/.test(e.message)) {
    promptUserForLabel(); // recover by asking again
  } else throw e;
}

Prevention

When it happens

Trigger: PATCH /workspace/:id/session-groups/:groupId with body.label missing, null, a number, or " " (whitespace only); any other route using requireStringField with an absent/empty field.

Common situations: Rename dialog submitted empty; client sends {name: ...} instead of {label: ...}; JSON.stringify drops an undefined label; form state initialized to null.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/4057a2752fda0ff1. Report an issue: GitHub.