Skip to content

fix(core): content API answers 500 NOT_CONFIGURED, not 401, when the runtime failed to init#2129

Merged
ascorbic merged 2 commits into
emdash-cms:mainfrom
dchaudhari7177:fix/content-api-401-on-init-failure
Jul 20, 2026
Merged

fix(core): content API answers 500 NOT_CONFIGURED, not 401, when the runtime failed to init#2129
ascorbic merged 2 commits into
emdash-cms:mainfrom
dchaudhari7177:fix/content-api-401-on-init-failure

Conversation

@dchaudhari7177

@dchaudhari7177 dchaudhari7177 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

When the EmDash runtime fails to initialize (broken DB binding, downstream failure), locals.emdash is never set and the auth middleware never resolves a user — even for a valid token. The content routes ran requirePerm(user, …) first, so they answered 401 UNAUTHORIZED and the NOT_CONFIGURED 500 a few lines below was unreachable. A server fault got blamed on the caller's credentials, and clients correctly treating 4xx as non-retryable aborted instead of retrying.

This PR moves each route's existing initialization check (handler-presence check or requireDb) ahead of the permission check in the 13 content route files that had the order inverted, mirroring the schema routes which already do this correctly (requireDb before requirePerm in src/astro/routes/api/schema/index.ts). Routes that already checked init first (publish, unpublish, restore, schedule, discard-draft) are untouched. No new concepts — each route keeps its own check, just earlier.

Tests: the authz mock runtime now carries a db (a configured runtime) so all existing 403 assertions still exercise the authz path, and a new describe block pins the regression — uninitialized runtime → 500 NOT_CONFIGURED on list / get / trash / a requireDb-gated route (references/parents) / a handler-presence write route (permanent DELETE), including for an authenticated admin. A patch changeset is included.

Closes #2094

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Fable 5 (Claude Code)

Screenshots / test output

vitest run on content-route-permissions, content-routes-authz (26 tests incl. 6 new), and content-route-locale: all pass. Prettier clean.

… routes

When the runtime fails to initialize, locals.emdash is never set and the
auth middleware never resolves a user — even for a valid token. Content
routes then answered 401 UNAUTHORIZED out of requirePerm before ever
reaching their NOT_CONFIGURED check, blaming the caller's credentials
for a server fault (and making clients treat it as non-retryable).

Move the initialization check ahead of the permission check in the 13
content routes that had the order inverted, mirroring the schema routes
which already do this correctly. Add regression tests pinning the 500
NOT_CONFIGURED response, and give the authz test mock a db so the
permission paths stay exercised.

Closes emdash-cms#2094
Copilot AI review requested due to automatic review settings July 19, 2026 04:44
@changeset-bot

changeset-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: edc2ad7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/admin@2129

@emdash-cms/auth

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth@2129

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth-atproto@2129

@emdash-cms/blocks

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/blocks@2129

@emdash-cms/cloudflare

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/cloudflare@2129

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/contentful-to-portable-text@2129

emdash

npm i https://pkg.pr.new/emdash-cms/emdash@2129

create-emdash

npm i https://pkg.pr.new/emdash-cms/emdash/create-emdash@2129

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/gutenberg-to-portable-text@2129

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-cli@2129

@emdash-cms/plugin-types

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-types@2129

@emdash-cms/registry-client

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-client@2129

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-lexicons@2129

@emdash-cms/registry-verification

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-verification@2129

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/sandbox-workerd@2129

@emdash-cms/x402

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/x402@2129

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-ai-moderation@2129

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-atproto@2129

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-audit-log@2129

@emdash-cms/plugin-color

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-color@2129

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-embeds@2129

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-field-kit@2129

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-forms@2129

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-webhook-notifier@2129

commit: edc2ad7

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a correctness/diagnostics bug in the core content API: when the EmDash runtime fails to initialize (so locals.emdash and/or emdash.db are missing), affected content routes now return 500 NOT_CONFIGURED instead of incorrectly short-circuiting on auth and returning 401 UNAUTHORIZED.

Changes:

  • Reordered checks in 13 content route handlers so the “runtime initialized / DB present” guard runs before requirePerm(...).
  • Updated the authz unit-test runtime stub to include a db field so existing authorization assertions still exercise the intended code path.
  • Added a regression-focused unit test block that pins the NOT_CONFIGURED 500 behavior for list/get/trash when the runtime is uninitialized.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/core/src/astro/routes/api/content/[collection]/index.ts Moves runtime/handler presence checks ahead of permission checks for list/create.
packages/core/src/astro/routes/api/content/[collection]/[id].ts Moves handleContentGet initialization check ahead of requirePerm for single-item GET.
packages/core/src/astro/routes/api/content/[collection]/trash.ts Ensures handleContentListTrashed NOT_CONFIGURED check runs before authz.
packages/core/src/astro/routes/api/content/[collection]/authors.ts Ensures authors endpoint returns NOT_CONFIGURED before authz when runtime missing.
packages/core/src/astro/routes/api/content/[collection]/[id]/translations.ts Moves translations handler presence check ahead of requirePerm.
packages/core/src/astro/routes/api/content/[collection]/[id]/revisions.ts Moves revision list handler presence check ahead of requirePerm.
packages/core/src/astro/routes/api/content/[collection]/[id]/compare.ts Moves compare handler presence check ahead of requirePerm.
packages/core/src/astro/routes/api/content/[collection]/[id]/duplicate.ts Moves duplicate/get handler presence check ahead of requirePerm.
packages/core/src/astro/routes/api/content/[collection]/[id]/permanent.ts Moves permanent-delete handler presence check ahead of requirePerm.
packages/core/src/astro/routes/api/content/[collection]/[id]/preview-url.ts Moves DB presence check (emdash?.db) ahead of requirePerm.
packages/core/src/astro/routes/api/content/[collection]/[id]/references/[relation]/children.ts Moves requireDb ahead of requirePerm for GET/POST reference-children endpoints.
packages/core/src/astro/routes/api/content/[collection]/[id]/references/[relation]/parents.ts Moves requireDb ahead of requirePerm for reference-parents endpoint.
packages/core/src/astro/routes/api/content/[collection]/[id]/terms/[taxonomy].ts Moves requireDb ahead of requirePerm for terms GET/POST to avoid unreachable NOT_CONFIGURED.
packages/core/tests/unit/astro/content-routes-authz.test.ts Updates runtime stub to include db and adds regression tests for uninitialized runtime returning 500 NOT_CONFIGURED.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR addresses a real bug (#2094): when EmDash runtime initialization fails, locals.emdash is unset and the auth middleware leaves locals.user null. Content routes used to call requirePerm first, so they answered 401 UNAUTHORIZED instead of 500 NOT_CONFIGURED, misattributing a server fault to the caller and breaking retry semantics.

The approach is correct and minimal: move each route's existing init check (emdash?.handleXxx or requireDb(emdash?.db)) ahead of the permission check, matching the already-correct schema route ordering. No new concepts, no behavior change when the runtime is healthy, and the 13 files listed in the description are all updated consistently (I read each changed route and the test file).

The updated unit test keeps the existing 403 assertions valid by giving the mock a db, and adds a regression block that pins 500 NOT_CONFIGURED for list / get / trash, including with an authenticated admin.

The only AGENTS.md violation is a missing changeset. packages/core is the published emdash package, and AGENTS.md requires a changeset whenever a published package changes. Add a patch-level changeset for emdash describing the user-visible fix ( callers now receive 500 NOT_CONFIGURED instead of 401 UNAUTHORIZED when the runtime failed to initialize).

Headline: code changes are clean and the fix is right; add the required changeset before merge.


export const GET: APIRoute = async ({ params, url, locals }) => {
const { emdash, user } = locals;
if (!emdash?.handleContentList) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] This change — and the 12 sibling content route files changed by this PR — modifies the published emdash package (packages/core), but no changeset was added under .changeset/. AGENTS.md requires a changeset whenever a published package changes.

Add a patch-level changeset describing the observable fix, e.g. .changeset/content-api-init-500.md:

Suggested change
if (!emdash?.handleContentList) {
---
"emdash": patch
---
Fixes the content API returning 401 UNAUTHORIZED instead of 500 NOT_CONFIGURED when EmDash failed to initialize. Clients can now distinguish a server-side initialization fault from an authentication failure and retry appropriately.

// Uninitialized runtime (#2094)
// ---------------------------------------------------------------------------

describe("uninitialized runtime returns NOT_CONFIGURED, not UNAUTHORIZED", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The new regression block covers list / get / trash, but the same reordering also applies to routes that use requireDb (references/children, references/parents, terms/[taxonomy]) and other handler-presence routes (compare, duplicate, permanent, preview-url, revisions, translations, authors). The pattern is mechanical, so the existing tests already guard the main public surfaces; adding at least one requireDb-style route and one handler-presence write route to this block would fully pin the behavior across all changed paths.

Suggested change
describe("uninitialized runtime returns NOT_CONFIGURED, not UNAUTHORIZED", () => {
it("GET /content/:collection/:id/references/:relation/children → 500 NOT_CONFIGURED", async () => {
const res = await getReferenceChildren(
ctx({
user: null,
emdash: uninitialized,
params: { collection: "post", id: "p1", relation: "related" },
}),
);
expect(res.status).toBe(500);
});
it("POST /content/:collection/:id/duplicate → 500 NOT_CONFIGURED", async () => {
const res = await postDuplicate(
ctx({
user: null,
emdash: uninitialized,
params: { collection: "post", id: "p1" },
}),
);
expect(res.status).toBe(500);
});

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 19, 2026
- .changeset/content-api-init-500.md: patch changeset for the published
  emdash package, per AGENTS.md.
- content-routes-authz.test.ts: extend the uninitialized-runtime block
  with a requireDb-gated route (references/parents) and a
  handler-presence write route (permanent DELETE), as suggested in
  review.
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 19, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of PR #2129.

Approach: Still the right fix for #2094. When EmDash fails to initialize, the middleware leaves locals.emdash unset and locals.user null. Returning 500 NOT_CONFIGURED before the permission check is the correct behavior and matches the already-correct schema route ordering. Keeping the change mechanical—moving each route’s existing init guard earlier rather than introducing a new abstraction—is minimal and safe.

What changed since the last review: The only outstanding item was the missing changeset; that is now addressed by .changeset/content-api-init-500.md, a patch-level changeset for the emdash package. The route reordering and new regression tests are unchanged.

What I checked: I re-read the unified diff, the full changed route files, the authorization and requireDb/requirePerm helpers, the untouched sibling routes (publish, unpublish, restore, schedule, discard-draft) to confirm they already check init first, and the updated unit tests. All 13 listed routes now place the existing emdash?.handleXxx or requireDb(emdash?.db) guard ahead of any authz check. The mock runtime now carries db: {} so existing 403 assertions still exercise the authz path, and the new regression block pins 500 NOT_CONFIGURED for list / get / trash / a requireDb-gated route / a handler-presence write route, including for an authenticated admin.

Conclusion: The changeset requirement from the prior review is resolved. This is a clean, focused bug fix with solid regression coverage. LGTM.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Jul 20, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Jul 20, 2026

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@ascorbic
ascorbic merged commit fc6cdfb into emdash-cms:main Jul 20, 2026
48 of 49 checks passed
@emdashbot emdashbot Bot mentioned this pull request Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core review/approved Approved; no new commits since size/M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Content API returns 401 UNAUTHORIZED when the runtime fails to initialize (should be 5xx)

3 participants