iOfficeAI/AionUi · error

[WebUI] /api/webui/reset-password returned no new_password

Error message

[WebUI] /api/webui/reset-password returned no new_password

What it means

Thrown when POST /api/webui/reset-password succeeds (2xx) but the JSON response contains no new_password field (neither data.new_password nor top-level new_password). The seeding flow cannot capture a credential to show the user.

Source

Thrown at packages/desktop/src/process/bridge/webuiBridge.ts:74

  }
  const statusRes = await fetch(`http://127.0.0.1:${port}/api/auth/status`);
  if (!statusRes.ok) {
    throw new Error(`[WebUI] /api/auth/status returned ${statusRes.status}`);
  }
  const statusJson = (await statusRes.json()) as { needs_setup?: boolean; data?: { needs_setup?: boolean } };
  const needsSetup = statusJson.needs_setup ?? statusJson.data?.needs_setup ?? false;
  if (!needsSetup) {
    setDesktopWebUIInitialPassword(undefined);
    return;
  }
  const resetRes = await fetch(`http://127.0.0.1:${port}/api/webui/reset-password`, { method: 'POST' });
  if (!resetRes.ok) {
    throw new Error(`[WebUI] /api/webui/reset-password returned ${resetRes.status}`);
  }
  const resetJson = (await resetRes.json()) as { data?: { new_password?: string }; new_password?: string };
  const newPassword = resetJson.data?.new_password ?? resetJson.new_password;
  if (!newPassword) {
    throw new Error('[WebUI] /api/webui/reset-password returned no new_password');
  }
  setDesktopWebUIInitialPassword(newPassword);
}

export function initWebuiBridge(): void {
  ipcBridge.webui.getStatus.provider(async () => {
    const snapshot = getDesktopWebUIStatus();
    const adminUsername = await fetchAdminUsername();
    return { ...snapshot, adminUsername };
  });

  ipcBridge.webui.start.provider(async (params) => {
    await maybeSeedInitialPassword();
    const handle = await startDesktopWebUI({
      port: params?.port,
      allowRemote: params?.allowRemote,
    });
    ipcBridge.webui.statusChanged.emit({

View on GitHub (pinned to 711aa0550e)

Solutions

  1. Inspect the actual response body (add a log of resetJson before throwing)
  2. Align the desktop client with the backend's current response schema (data.new_password vs new_password)
  3. Pin or upgrade together desktop and aioncore backend versions so schemas match
  4. Add backend-side contract tests for the reset-password response shape

Example fix

// before
const newPassword = resetJson.data?.new_password ?? resetJson.new_password;

// after (also accept token-style shapes and log for diagnosis)
const newPassword = resetJson.data?.new_password ?? resetJson.new_password ?? resetJson.data?.password;
if (!newPassword) {
  console.error('[WebUI] reset-password unexpected payload:', JSON.stringify(resetJson));
  throw new Error('[WebUI] /api/webui/reset-password returned no new_password');
}
Defensive patterns

Strategy: type-guard

Validate before calling

const json = (await resetRes.json()) as { data?: { new_password?: string } };
if (!json.data?.new_password) { /* log payload, surface schema mismatch clearly */ }

Type guard

function hasNewPassword(v: unknown): v is { data: { new_password: string } } {
  return typeof (v as { data?: { new_password?: unknown } })?.data?.new_password === 'string';
}

Try / catch

catch (err) { log.error('[WebUI] reset payload:', err.message, rawBody); throw err; }

Prevention

When it happens

Trigger: Backend returns 200 with a differently shaped payload, e.g. {data:{}} or a renamed field, so newPassword is undefined.

Common situations: Backend API version drift where the response schema changed (field renamed, nesting moved), or an error payload returned with 200 status.

Related errors


AI-assisted analysis of iOfficeAI/AionUi@711aa0550e (2026-08-28). Data as JSON: /api/errors/69cdb1809e6ab47b. Report an issue: GitHub.