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

Last of the Astro v6 removals: `Astro.locals.runtime.ctx` (the ExecutionContext with waitUntil, passThroughOnException, caches accessor) is gone. The trap getter throws and points to the replacement, `Astro.locals.cfContext`, which the v6 adapter populates with the same ExecutionContext.

Solutions

  1. Replace `Astro.locals.runtime.ctx` with `Astro.locals.cfContext`.
  2. Update the App.Locals type to declare `cfContext: ExecutionContext` for type safety.
  3. Sweep for the other removed properties (env, cf, caches) at the same time.
  4. Remain on Astro v5 with the matching adapter if migration must wait.

Example fix

// before
Astro.locals.runtime.ctx.waitUntil(sendAnalytics());

// after
Astro.locals.cfContext.waitUntil(sendAnalytics());
Defensive patterns

Strategy: type-guard

Type guard

interface CfLocals extends App.Locals {
  cfContext?: ExecutionContext;
}
function safeExecContext(locals: App.Locals): ExecutionContext | undefined {
  const v6 = (locals as CfLocals).cfContext;
  if (v6) return v6;
  const runtime = (locals as Record<string, unknown>).runtime;
  if (!runtime || typeof runtime !== 'object') return undefined;
  try {
    return (runtime as { ctx: unknown }).ctx as ExecutionContext;
  } catch {
    return undefined; // v6 trap — migrate to Astro.locals.cfContext
  }
}

Prevention

When it happens

Trigger: Calling `Astro.locals.runtime.ctx.waitUntil(...)` (analytics beacons, deferred cache fills) in an Astro v6 project throws the moment the getter executes.

Common situations: Deferred-work patterns from Astro v5 projects; code copied from older Cloudflare adapter docs that used locals.runtime.ctx.

Related errors


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

Appendix: 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 52e6c34790)