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
- Inspect the failing request URL in the server logs and confirm all three path segments (extensionId, assetSessionId, assetPath) are present and non-empty.
- Trace the URL back to its producer (buildMountedStaticAssetPath / createAssetSession return value) and ensure none of the inputs fed to it are undefined or empty.
- Verify the asset session still exists and that the cookie name derived from createStaticAssetSessionCookieName(assetSessionId) matches what the client sends.
- 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
- Always build asset URLs through buildMountedStaticAssetPath rather than string concatenation so segments can't be silently dropped.
- Treat a falsy return from buildMountedStaticAssetPath as an error at the producer side, not the request side.
- Invalidate persisted asset URLs when the underlying session changes.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- EXTENSION_ASSET_METHOD_NOT_ALLOWED
- Extension asset server base URL is unavailable; start the as
- mcp server is not running: ${serverName}
- Extension manifest not found: ${extensionId}
- Auth request failed (${response.status})
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/1bcc5b40bf3bcdce.
Report an issue: GitHub.