{"record":{"id":"0e8a47f90434141f","repo":"immich-app/immich","slug":"failed-to-call-host-function-string-name-re","errorCode":null,"errorMessage":"Failed to call host function \"${String(name)}\", received ${result.status} - ${JSON.stringify(result.message)}","messagePattern":"Failed to call host function \"(.+?)\", received (.+?) - (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk/src/host-functions.ts","lineNumber":68,"sourceCode":"] as const;\n\nexport const hostFunctions = (authToken: string) => {\n  const host = Host.getFunctions();\n  type HostFunctionName = keyof typeof host;\n\n  const call = <T, R>(name: HostFunctionName, authToken: string, args: T) => {\n    const pointer1 = Memory.fromString(JSON.stringify({ authToken, args }));\n    const fn = host[name];\n    const handler = Memory.find(fn(pointer1.offset));\n\n    try {\n      const result = JSON.parse(handler.readString()) as HostFunctionResult<R>;\n\n      if (result.success) {\n        return result.response;\n      }\n\n      throw new Error(\n        `Failed to call host function \"${String(name)}\", received ${result.status} - ${JSON.stringify(result.message)}`,\n      );\n    } finally {\n      handler.free();\n      pointer1.free();\n    }\n  };\n\n  return {\n    // album\n    searchAlbums: (dto: AlbumSearchDto) =>\n      call<[AlbumSearchDto], AlbumResponseDto[]>('searchAlbums', authToken, [\n        dto,\n      ]),\n    createAlbum: (dto: CreateAlbumDto) =>\n      call<[CreateAlbumDto], AlbumResponseDto>('createAlbum', authToken, [dto]),\n    addAssetsToAlbum: (albumId: string, assetIds: string[]) =>\n      call<[string, BulkIdsDto], BulkIdResponseDto[]>(","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/packages/plugin-sdk/src/host-functions.ts#L50-L86","documentation":"Inside a plugin (WASM module loaded via extism), hostFunctions(authToken) wraps calls into the Immich host process. Each call serializes { authToken, args } to a memory pointer, invokes the host function, and parses the returned HostFunctionResult. When the host responds with { success: false, status, message } the wrapper throws an Error echoing the host HTTP status code and message. This is the plugin-side surface for any host rejection (auth, validation, not-found, server error).","triggerScenarios":"Calling hostFunctions(token).searchAlbums(dto), createAlbum, addAssetsToAlbum, addAssetsToAlbums, or httpRequest when the authToken is invalid/expired, the DTO fails host-side validation, the album/asset does not exist, or the host returns any non-success status.","commonSituations":"Plugin runs with a stale or wrong-scoped auth token; the host Immich server is degraded; a deleted album/asset is referenced; an httpRequest to an external URL returns a non-2xx status.","solutions":["Inspect the status/message embedded in the thrown error text to map it to the host-side cause (401 -> token, 404 -> missing resource, 4xx -> DTO, 5xx -> host fault).","Ensure the plugin is invoked with a valid, sufficiently-permissioned auth token for the operations it performs.","Validate DTOs (e.g. non-empty assetIds, existing albumId) before calling the host function.","For httpRequest, confirm the target URL is reachable and returns 2xx from the host network position."],"exampleFix":"// before\nconst albums = host.searchAlbums({ albumName: 'x' }); // may throw\n// after\ntry {\n  const albums = host.searchAlbums({ albumName: 'x' });\n} catch (e) {\n  throw new Error(`album search failed in plugin: ${(e as Error).message}`);\n}","handlingStrategy":"try-catch","validationCode":"// validate inputs before calling host functions\nconst assertNonEmpty = (v: unknown, name: string) => {\n  if (!v || (Array.isArray(v) && v.length === 0))\n    throw new Error(`${name} must be provided`);\n};\nassertNonEmpty(dto.albumName, 'albumName');","typeGuard":"const isHostError = (e: unknown): e is Error =>\n  e instanceof Error && e.message.startsWith('Failed to call host function');","tryCatchPattern":"try {\n  const albums = host.searchAlbums(dto);\n} catch (e) {\n  if (isHostError(e)) {\n    // parse status from message, degrade gracefully\n    return [];\n  }\n  throw e;\n}","preventionTips":["Pass a freshly-validated auth token scoped to the operations the plugin performs.","Validate DTOs (ids exist, arrays non-empty) before invoking host functions.","Wrap every host call and surface a plugin-specific error with context."],"tags":["plugin-sdk","host-functions","wasm","extism","authentication"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}