{"record":{"id":"ce9264eeaf259a72","repo":"immich-app/immich","slug":"authtoken-is-required","errorCode":null,"errorMessage":"authToken is required","messagePattern":"authToken is required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/workflow-execution.service.ts","lineNumber":170,"sourceCode":"  }\n\n  private getPluginKey({ id, hostFunctions }: { id: string; hostFunctions: boolean }) {\n    return id + (hostFunctions ? '/worker' : '');\n  }\n\n  private wrap<T>(fn: (authDto: AuthDto, context: HostContext, args: T) => Promise<unknown>) {\n    return async (plugin: CurrentPlugin, offset: bigint) => {\n      try {\n        const handle = plugin.read(offset);\n        if (!handle) {\n          return plugin.store(\n            JSON.stringify({ success: false, status: 400, message: 'Called host function without input' }),\n          );\n        }\n\n        const { authToken, args } = handle.json() as { authToken: string; args: T };\n        if (!authToken) {\n          throw new Error('authToken is required');\n        }\n\n        const context = plugin.hostContext<HostContext>();\n        const authDto = this.validate(authToken);\n        const response = await fn(authDto, context, args);\n\n        return plugin.store(JSON.stringify({ success: true, response }));\n      } catch (error: Error | any) {\n        if (error instanceof HttpException) {\n          this.logger.error(`Plugin host exception: ${error}`);\n          return plugin.store(\n            JSON.stringify({ success: false, status: error.getStatus(), message: error.getResponse() }),\n          );\n        }\n\n        this.logger.error(`Plugin host exception: ${error}`, error?.stack);\n\n        return plugin.store(","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/workflow-execution.service.ts#L152-L188","documentation":"A plain Error thrown inside the wrap() host-function dispatcher when the JSON input read from the plugin handle does not contain an authToken field. The wrap function reads the handle, parses { authToken, args }, and requires a truthy authToken before validating it as a JWT. Without it, the call is rejected before any authentication attempt.","triggerScenarios":"A plugin calls a host function (searchAlbums, createAlbum, httpRequest, etc.) but the input payload it writes to memory omits authToken. This usually means the plugin SDK code did not forward the workflow authToken from the WorkflowEventPayload into the host call input.","commonSituations":"Plugin SDK integration bug where the payload's workflow.authToken is not propagated; plugin authored against an older SDK that did not require authToken; manually constructed host-call input missing the field.","solutions":["Ensure the plugin reads workflow.authToken from the WorkflowEventPayload and includes it in every host function call input.","Update the plugin SDK to a version that injects authToken automatically.","Validate, in plugin tests, that host call inputs contain a non-empty authToken before dispatch.","Rebuild and re-import the WASM plugin after fixing the SDK wiring."],"exampleFix":"// before (plugin pseudo-code)\nconst result = host.call('searchAlbums', JSON.stringify({ args: [dto] }));\n\n// after\nconst result = host.call('searchAlbums', JSON.stringify({ authToken: payload.workflow.authToken, args: [dto] }));","handlingStrategy":"validation","validationCode":"// Plugin-side: ensure authToken is present before any host call\nfunction buildHostInput(payload, args) {\n  if (!payload?.workflow?.authToken) {\n    throw new Error('authToken missing from workflow payload');\n  }\n  return { authToken: payload.workflow.authToken, args };\n}","typeGuard":"const hasAuthToken = (input: unknown): input is { authToken: string } =>\n  typeof input === 'object' && input !== null && typeof (input as any).authToken === 'string' && (input as any).authToken.length > 0;","tryCatchPattern":"// On the server wrap() this becomes a failure response; detect it in the plugin\nconst result = host.call('searchAlbums', JSON.stringify(buildHostInput(payload, args)));\nif (!result.success && result.message === 'authToken is required') {\n  abortWorkflow('authToken not propagated');\n}","preventionTips":["Always forward payload.workflow.authToken into host-call inputs.","Keep the plugin SDK updated so authToken injection is automatic.","Add a plugin unit test asserting host inputs contain a non-empty authToken."],"tags":["workflow","plugin","authentication","extism","host-functions"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}