Skip to content

Chuck Errors

How Strapi reports failures and what each class of error usually means in this deployment. Strapi 5 returns errors via the built-in strapi::errors middleware, which is enabled in apps/chuck/meat/config/middlewares.ts.

All /api/* errors are JSON of the shape:

{
"data": null,
"error": {
"status": 404,
"name": "NotFoundError",
"message": "Not Found",
"details": {}
}
}

The name value is the most useful field for grepping logs.

Status error.name Likely cause First action
400 ValidationError Required field missing on create/update; regex on slug rejected; component shape mismatch. Inspect error.details.errors[] — each has path, message.
401 UnauthorizedError Missing Authorization: Bearer ... on a protected route; admin session expired. Confirm consumer is sending STRAPI_TOKEN (brisket: apps/brisket/src/env.js).
403 ForbiddenError API token lacks permission for this content type / action; Public role missing find/findOne. Open /admin → Settings → Users & Permissions → API Tokens / Roles → Public. Re-issue token with required scopes.
404 NotFoundError Single-type not yet populated (Strapi returns 404 until a record exists); :id does not exist on a collection type; URL slug typo. Check the entry exists and is published (every type has draftAndPublish: true).
405 MethodNotAllowedError Calling a non-default verb on a default router (e.g. POST /api/blog-section when only find is enabled). Use the documented verbs in chuck-api.
409 ConflictError Unique constraint violation (e.g. duplicate slug). Pick a unique value.
413 PayloadTooLargeError Upload bigger than the configured limit; Strapi default body limit (strapi::body) is 100kb for JSON, larger for multipart. apps/chuck/meat/config/middlewares.ts registers strapi::body with no override, so Strapi defaults apply. TODO(@marty): confirm whether the runtime deployment injects body-limit overrides via env.
415 UnsupportedMediaTypeError Wrong Content-Type on upload; provider mismatch. Use multipart/form-data for media; application/json otherwise.
422 ValidationError (alt) Same as 400 but for entity-service-level validation. Same triage as 400.
429 TooManyRequestsError Rate-limit at the platform layer (Cloudflare / Railway). Strapi itself does not ship a built-in rate limiter in this config. Inspect upstream proxy.
500 ApplicationError / InternalServerError Database connection lost, unhandled plugin error, R2 upload failure. Logs first, then chuck-runbook.
503 n/a Process not ready (boot in progress) or dependency down (DB / R2). Healthcheck path; restart.

Field-level validation that the schemas in apps/chuck/meat/src/api/*/content-types/*/schema.json enforce:

  • required: true — many shared-data fields are required (e.g. welcome-shared-data.websiteName, mobileLogo, description, keywords). Saving without them returns 400 ValidationError.
  • regex on slugs — blog.slug and blog-blog.slug enforce ^[a-z0-9]+(?:-[a-z0-9]+)*$. Spaces, capitals, or trailing hyphens fail validation.
  • allowedTypes on media — images, files, videos mostly. Uploads of other MIME types are rejected at media validation.
  • Components marked repeatable: true, required: true — empty arrays fail (e.g. welcome-faq-section.faqSection).

When debugging an editor-reported “save failed”, grep for the field path in error.details.errors[*].path and cross-reference the schema file.

Failures bubble up from strapi-provider-cloudflare-r2 as 500s with provider messages. Common causes:

  • Access Denied / 403 — bad CF_ACCESS_KEY_ID / CF_ACCESS_SECRET, or the API token lacks Object:Write on the bucket.
  • NoSuchBucket — CF_BUCKET does not match the configured bucket; check Cloudflare dashboard.
  • CSP block on the rendered page — uploads succeed but images refuse to render in the admin or in brisket. Check that CF_PUBLIC_ACCESS_URL matches the live R2 public hostname; CSP is built from this in config/middlewares.ts.
  • Timeouts — large media on slow connections. The repo does not override the strapi::body middleware in apps/chuck/meat/config/middlewares.ts, so Strapi defaults apply. TODO(@marty): confirm whether the runtime deployment increases the upload body limit at the proxy/Strapi layer.

Failures appear at admin-panel “send test email” or on users-permissions password-reset:

  • ESOCKET / ECONNREFUSED — SMTP_HOST / SMTP_PORT wrong or egress blocked.
  • EAUTH — SMTP_USER / SMTP_PASSWORD rejected.
  • From address rejected — provider requires a verified sender; the hard-coded defaultFrom: "no-reply@foxy.ai" in config/plugins.ts must be a verified address on the upstream provider.

The plugin is installed but largely unused. If end-user registration is enabled, expect:

  • ValidationError: email is already taken
  • ValidationError: password is too weak
  • ApplicationError: Invalid identifier or password

From pg driver (Postgres) or better-sqlite3:

  • ECONNREFUSED on boot — DATABASE_URL host unreachable, or Postgres not running locally. See chuck-local-dev.
  • password authentication failed — DATABASE_PASSWORD mismatch.
  • relation "..." does not exist — schema drift. Strapi runs migrations on boot; if the DB is older than the code, pnpm strapi build && pnpm start should reconcile. If not, see chuck-runbook.
  • acquireConnectionTimeout — connection pool exhausted. Bump DATABASE_POOL_MAX (default 10) or investigate slow queries.
  • Missing API_TOKEN_SALT etc. — required env not set. See chuck-env.
  • Cannot find module 'strapi-provider-cloudflare-r2' — node_modules out of sync; re-run pnpm install from apps/chuck/meat/.
  • Node engine error — package.json requires Node 24 LTS. Run nvm use from the repository root, then reinstall dependencies.

Strapi uses Koa with the strapi::logger middleware. There is no OpenTelemetry exporter wired in apps/chuck/meat/config/ (grep -r opentelemetry apps/chuck returns no hits). TODO(@marty): document how chuck logs reach centralized logging — no log shipper or OTel exporter is checked in; see operations/observability for the service-level matrix.