srbhr/Resume-Matcher · error · Error

Failed to load feature prompts (status ${res.status}).

Error message

Failed to load feature prompts (status ${res.status}).

What it means

fetchFeaturePrompts performs GET /config/feature-prompts and throws this generic Error whenever the HTTP response is not ok. Like the other config fetchers it only surfaces the status code; the actual cause (auth, routing, backend failure) must be inferred from it. The typed FeaturePrompts result is only returned on a 2xx with a JSON body.

Source

Thrown at apps/frontend/lib/api/config.ts:369

  code: 'missing_placeholders';
  field: 'cover_letter_prompt' | 'outreach_message_prompt';
  missing: string[];
}

export class FeaturePromptsError extends Error {
  detail: FeaturePromptsValidationError;

  constructor(detail: FeaturePromptsValidationError) {
    super(`Invalid ${detail.field}: missing ${detail.missing.join(', ')}`);
    this.name = 'FeaturePromptsError';
    this.detail = detail;
  }
}

export async function fetchFeaturePrompts(): Promise<FeaturePrompts> {
  const res = await apiFetch('/config/feature-prompts', { credentials: 'include' });
  if (!res.ok) {
    throw new Error(`Failed to load feature prompts (status ${res.status}).`);
  }
  return res.json();
}

export async function updateFeaturePrompts(update: FeaturePromptsUpdate): Promise<FeaturePrompts> {
  const res = await apiFetch('/config/feature-prompts', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',
    body: JSON.stringify(update),
  });

  if (!res.ok) {
    // Error path: body may be absent or malformed, so we tolerate parse
    // failure. A fetch body is a one-shot stream — read it once and reuse
    // for both the 422-special-case and the generic fallback.
    const errBody = (await res.json().catch(() => ({}))) as {
      detail?: FeaturePromptsValidationError | string;

View on GitHub (pinned to 116f9cc3b0)

Solutions

  1. Check the status in the message: 401/403 → refresh the session; 404 → confirm /api/v1/config/feature-prompts exists on the backend and rewrites point at BACKEND_ORIGIN; 5xx → inspect backend logs.
  2. Curl the endpoint with the session cookie to separate proxy problems from backend problems.
  3. Verify frontend/backend versions are matched (endpoint present in the deployed backend).
  4. Handle the rejection where the callers (llmConfig/featureConfig/promptConfig/featurePrompts/keyStatus loaders) run so the page degrades gracefully.

Example fix

// before
const prompts = await fetchFeaturePrompts();

// after
let prompts: FeaturePrompts;
try {
  prompts = await fetchFeaturePrompts();
} catch (e) {
  console.warn('feature prompts unavailable, using defaults', e);
  prompts = DEFAULT_FEATURE_PROMPTS; // fallback
}
Defensive patterns

Strategy: fallback

Validate before calling

// Health-check the backend before dependent config fetches
const ping = await fetch('/api/v1/status', { credentials: 'include' });
if (!ping.ok) throw new Error('Backend unavailable, skip feature prompts load');

Type guard

function isFeaturePrompts(x: unknown): x is FeaturePrompts {
  return typeof x === 'object' && x !== null && 'cover_letter_prompt' in x;
}

Try / catch

try {
  prompts = await fetchFeaturePrompts();
} catch {
  prompts = DEFAULT_FEATURE_PROMPTS;
}

Prevention

When it happens

Trigger: Non-2xx on GET /config/feature-prompts: 401/403 from expired credentials cookie or CSRF check, 404 because the backend route is missing or NEXT_PUBLIC_API_URL/BACKEND_ORIGIN proxying is wrong, 5xx when the backend cannot read its feature-prompt store (e.g. DB/config file unavailable).

Common situations: Opening Settings while the backend container is restarting (502 through the proxy); stale deployment where frontend and backend versions disagree and the endpoint moved; running the frontend without the backend on :8000 in local dev.

Related errors


AI-assisted analysis of srbhr/Resume-Matcher@116f9cc3b0 (2026-08-28). Data as JSON: /api/errors/42d2ee53a8ed77ad. Report an issue: GitHub.