withastro/astro · error · Error

Astro.locals.runtime.ctx has been removed in Astro v6. Use '

Error message

Astro.locals.runtime.ctx has been removed in Astro v6. Use 'Astro.locals.cfContext' instead.

What it means

Astro v6 removed the `Astro.locals.runtime.ctx` accessor from the Cloudflare adapter. The `runtime` object is now a non-enumerable property whose getters all throw to force migration. `ctx` (the Cloudflare ExecutionContext used for `waitUntil`, `passThroughOnException`, etc.) was moved to `Astro.locals.cfContext`. The throw is intentional and fires the moment user code reads the property.

Source

Thrown at packages/integrations/cloudflare/src/utils/cf-helpers.ts:87

		enumerable: false,
		value: {
			get env(): never {
				throw new Error(
					`Astro.locals.runtime.env has been removed in Astro v6. Use 'import { env } from "cloudflare:workers"' instead.`,
				);
			},
			get cf(): never {
				throw new Error(
					`Astro.locals.runtime.cf has been removed in Astro v6. Use 'Astro.request.cf' instead.`,
				);
			},
			get caches(): never {
				throw new Error(
					`Astro.locals.runtime.caches has been removed in Astro v6. Use the global 'caches' object instead.`,
				);
			},
			get ctx(): never {
				throw new Error(
					`Astro.locals.runtime.ctx has been removed in Astro v6. Use 'Astro.locals.cfContext' instead.`,
				);
			},
		},
	});
	return locals;
}

/**
 * Extracts the client IP address from the `cf-connecting-ip` header.
 */
export function getClientAddress(request: Request): string | undefined {
	return getValidatedIpFromHeader(request.headers.get('cf-connecting-ip'));
}

View on GitHub (pinned to d081033d5f)

Solutions

  1. Replace every `Astro.locals.runtime.ctx` with `Astro.locals.cfContext` — the object shape (ExecutionContext) is identical.
  2. If you only need `waitUntil`, use `Astro.locals.cfContext.waitUntil(...)`.
  3. Grep the codebase for `locals.runtime` and migrate `env`, `cf`, `caches`, and `ctx` to their v6 replacements (`import { env } from 'cloudflare:workers'`, `Astro.request.cf`, the global `caches`, and `Astro.locals.cfContext`).
  4. If a third-party integration reads `runtime.ctx`, upgrade it or wrap it so it consumes `cfContext`.

Example fix

// before
Astro.locals.runtime.ctx.waitUntil(promise);
const cfCtx = Astro.locals.runtime.ctx;

// after
Astro.locals.cfContext.waitUntil(promise);
const cfCtx = Astro.locals.cfContext;
Defensive patterns

Strategy: validation

Validate before calling

const hasCfContext = !!(Astro.locals as any).cfContext;
if (!hasCfContext) throw new Error('Cloudflare cfContext missing — wrong adapter?');
const ctx = (Astro.locals as any).cfContext;

Type guard

function hasCfContext(locals: Record<string, unknown>): locals is { cfContext: ExecutionContext } {
  return typeof (locals as any).cfContext === 'object' && (locals as any).cfContext !== null;
}

Try / catch

// Avoid catching — migrate the accessor. Catching hides the migration need.
// If you must guard third-party code:
function safeCtx(locals: any) {
  try { return locals.cfContext; } catch { return undefined; }
}

Prevention

When it happens

Trigger: User page/middleware/API code on a Cloudflare adapter deployment accesses `Astro.locals.runtime.ctx` (e.g. `Astro.locals.runtime.ctx.waitUntil(p)`). The getter is defined with `get ctx(): never`, so any read — even `typeof` checks, destructuring, or serialization — synchronously throws.

Common situations: Upgrading `@astrojs/cloudflare` to a v6-compatible release while keeping pre-v6 middleware or endpoints that referenced `runtime.ctx`. Copying older Cloudflare adapter snippets from docs or blogs. SSR code that stored `const { ctx } = Astro.locals.runtime` at module/request scope.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/94eee2c8c2c52a18. Report an issue: GitHub.