moeru-ai/airi · error · HttpError

EXTENSION_ASSET_REQUEST_INVALID

EXTENSION_ASSET_REQUEST_INVALID

Error message

Unauthorized

What it means

This HttpError (status 401, code EXTENSION_ASSET_REQUEST_INVALID) is thrown by the extension static-asset HTTP route when the parsed request path is missing one or more of the three required segments: extensionId, assetSessionId, or assetPath. The route parses the incoming URL via parseStaticAssetRequestPath and defaults each segment to an empty string; if any remains empty the request is rejected as unauthorized because the server cannot correlate it to a valid asset session.

Source

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

      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',
        })
      }

      const cookieValue = getCookie(event, createStaticAssetSessionCookieName(assetSessionId))
      const auth = await options.authorize({
        extensionId,
        assetSessionId,
        assetPath,
        cookieValue,
      })
      if (!auth.ok) {
        throw auth.error
      }

View on GitHub (pinned to 27111382b4)

Solutions

  1. Inspect the failing request URL in the server logs and confirm all three path segments (extensionId, assetSessionId, assetPath) are present and non-empty.
  2. Trace the URL back to its producer (buildMountedStaticAssetPath / createAssetSession return value) and ensure none of the inputs fed to it are undefined or empty.
  3. Verify the asset session still exists and that the cookie name derived from createStaticAssetSessionCookieName(assetSessionId) matches what the client sends.
  4. If the URL comes from persisted state (bookmarks, saved HTML), invalidate and regenerate it from a fresh createAssetSession call.

Example fix

// before
const src = `/static-assets/${extensionId}/${assetPath}`

// after
import { buildMountedStaticAssetPath } from '...'
const src = buildMountedStaticAssetPath({
  extensionId,
  assetSessionId: session.assetSessionId,
  assetPath,
})
if (!src) throw new Error('asset path could not be built — check inputs')
Defensive patterns

Strategy: validation

Validate before calling

// Validate the three required segments before constructing/requesting the asset URL
function isValidAssetRequest(p: { extensionId?: string, assetSessionId?: string, assetPath?: string }): boolean {
  return Boolean(p.extensionId && p.assetSessionId && p.assetPath)
}

if (!isValidAssetRequest({ extensionId, assetSessionId, assetPath })) {
  throw new Error('Cannot request asset: extensionId, assetSessionId, and assetPath are all required')
}

Type guard

function isCompleteAssetRef(ref: unknown): ref is { extensionId: string, assetSessionId: string, assetPath: string } {
  return typeof ref === 'object' && ref !== null
    && typeof (ref as any).extensionId === 'string' && (ref as any).extensionId !== ''
    && typeof (ref as any).assetSessionId === 'string' && (ref as any).assetSessionId !== ''
    && typeof (ref as any).assetPath === 'string' && (ref as any).assetPath !== ''
}

Prevention

When it happens

Trigger: A GET request to the static-asset endpoint whose URL path does not match the expected /<extensionId>/<assetSessionId>/<assetPath...> shape — e.g. a truncated URL, a malformed bookmark/iframe src, or a request constructed by code that forgot to include the assetSessionId segment produced by createAssetSession.

Common situations: An extension renders an asset URL with a missing piece (extensionId unknown at render time), a browser tab holding a stale URL after the session expired, manual testing with a hand-typed URL, or a bug in buildMountedStaticAssetPath producing an empty component.

Understand the failure class

Related errors


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