moeru-ai/airi · warning · HttpError

EXTENSION_ASSET_METHOD_NOT_ALLOWED

EXTENSION_ASSET_METHOD_NOT_ALLOWED

Error message

Method Not Allowed

What it means

An HttpError with status 405 thrown by the extension static-asset route handler when the request method is neither GET nor HEAD. The route exists to serve plugin iframe assets (/_airi/extensions/:extensionId/sessions/:assetSessionId/ui/**assetPath) and intentionally rejects mutating verbs before any auth/asset resolution. The stable code EXTENSION_ASSET_METHOD_NOT_ALLOWED lets callers distinguish it from other 405s.

Source

Thrown at apps/stage-tamagotchi/src/main/services/airi/http-server/static-assets/route.ts:50

 * Use when:
 * - Serving plugin iframe assets under `/_airi/extensions/:extensionId/sessions/:assetSessionId/ui/**assetPath`
 *
 * Expects:
 * - Cookie-backed asset session data to be present and valid
 * - `resolveAsset` to map request params into a validated local file
 *
 * Returns:
 * - H3 event handler that enforces cookie auth before static file response
 */
export function createStaticAssetRoute(options: StaticAssetRouteOptions) {
  return eventHandler(async (event) => {
    try {
      Object.entries(staticAssetSecurityHeaders).forEach(([key, value]) => {
        event.res.headers.set(key, value)
      })

      if (event.req.method !== 'GET' && event.req.method !== 'HEAD') {
        throw new HttpError({
          status: 405,
          code: 'EXTENSION_ASSET_METHOD_NOT_ALLOWED',
          message: 'Method Not Allowed',
        })
      }

      const requestPath = parseStaticAssetRequestPath(getRequestURL(event).pathname)
      const extensionId = requestPath?.extensionId ?? ''
      const assetSessionId = requestPath?.assetSessionId ?? ''
      const assetPath = normalizeStaticAssetPath(requestPath?.assetPath ?? '')

      if (!extensionId || !assetSessionId || !assetPath) {
        throw new HttpError({
          status: 401,
          code: 'EXTENSION_ASSET_REQUEST_INVALID',
          message: 'Unauthorized',
          reason: 'required extensionId, assetSessionId, or assetPath is missing',
        })

View on GitHub (pinned to 27111382b4)

Solutions

  1. Issue only GET (or HEAD) requests to extension static-asset URLs; they serve files, not actions.
  2. Point mutating plugin API calls at the dedicated extension API route, not the ui/** asset path.
  3. If you need OPTIONS support for CORS preflight, add an explicit OPTIONS handler rather than relying on the asset route.
  4. Inspect the request method and URL in the browser network panel to confirm the wrong verb/endpoint combination.

Example fix

// before
fetch(`/_airi/extensions/${extId}/sessions/${sid}/ui/index.html`, { method: 'POST' })

// after
fetch(`/_airi/extensions/${extId}/sessions/${sid}/ui/index.html`, { method: 'GET' })
Defensive patterns

Strategy: validation

Validate before calling

function isAllowedAssetMethod(method: string): boolean {
  return method === 'GET' || method === 'HEAD'
}

if (!isAllowedAssetMethod(request.method)) {
  // point the client at the extension API route instead
}

Type guard

function isAllowedAssetMethod(method: string): boolean {
  return method === 'GET' || method === 'HEAD'
}

Try / catch

try {
  await fetch(assetUrl, { method })
}
catch (error) {
  if (error?.code === 'EXTENSION_ASSET_METHOD_NOT_ALLOWED') {
    // switch to GET, or route the call to the extension API endpoint
  }
}

Prevention

When it happens

Trigger: Any POST/PUT/DELETE/PATCH/OPTIONS request hitting the static-asset route URL. Because the check runs after security headers are set but before auth and path parsing, it fires uniformly for all non-GET/HEAD methods regardless of credentials.

Common situations: A plugin iframe host posting a form to an asset URL by mistake; a reverse proxy health check using OPTIONS; a misconfigured client treating the asset endpoint as an upload target; browser preflight (OPTIONS) before a cross-origin request to the asset path.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/a3e28fa72b9537bc. Report an issue: GitHub.