{"record":{"id":"9db4dd747a4f13b2","repo":"HeyPuter/puter","slug":"forbidden-9db4dd","errorCode":"forbidden","errorMessage":"Prompt blocked by policy","messagePattern":"Prompt blocked by policy","errorType":"exception","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"src/backend/drivers/ai-chat/ChatCompletionDriver.ts","lineNumber":367,"sourceCode":"        };\n\n        await this.clients.event.emitAndWait(\n            'ai.prompt.validate',\n            validateEvent,\n            {},\n        );\n\n        // Blocked prompts get rerouted to fake-chat. With `event.abuse` we\n        // pick the `abuse` model, which embeds `event.custom` (phone-home\n        // script for bots, etc.) in its response so the bot's renderer\n        // executes it. Without `abuse`, we silently route to the default\n        // `fake` model (lorem-ipsum response). Mirrors v1 AIChatService.\n        let blocked = false;\n        if (!validateEvent.allow) {\n            const fakeModelId = validateEvent.abuse ? 'abuse' : 'fake';\n            const fakeModel = this.#resolveModel(fakeModelId, 'fake-chat');\n            if (!fakeModel) {\n                throw new HttpError(403, 'Prompt blocked by policy', {\n                    legacyCode: 'forbidden',\n                });\n            }\n            blocked = true;\n            model = fakeModel;\n            intendedProvider = 'fake-chat';\n            if (typeof validateEvent.custom !== 'undefined') {\n                args.custom = validateEvent.custom;\n            }\n        }\n\n        // -- Credit / subscription gates (metering) --------------------\n        // Cheap pre-flight: reject when the user can't afford even the\n        // approximate input cost, keep subscriber-only models gated, and\n        // cap `max_tokens` so output can't exceed remaining credits.\n        // Skipped for blocked requests since fake-chat is free and the\n        // user shouldn't see a billing error in place of the abuse page.\n        if (!blocked) {","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/drivers/ai-chat/ChatCompletionDriver.ts#L349-L385","documentation":"Thrown (HTTP 403, legacy code 'forbidden') when an ai.prompt.validate listener flagged the prompt as disallowed (allow=false, e.g. content moderation/abuse) but no fake-chat model ('fake' or 'abuse') is registered in config. Normally a blocked prompt is silently rerouted to a fake-chat model that returns lorem-ipsum (or a phone-home script for abuse); this throw is the fallback when that reroute target is itself missing. It signals a server-side configuration gap, not a client content violation.","triggerScenarios":"An event handler attached to 'ai.prompt.validate' sets event.allow=false on a request, and the model registry has no entry resolvable by ChatCompletionDriver.#resolveModel(fakeModelId, 'fake-chat') for either the 'fake' or 'abuse' id. Triggered by any chat completion whose prompt matches the moderation/abuse predicate.","commonSituations":"Self-hosted Puter installs where the fake-chat models were stripped from config.json but a moderation extension is still enabled; or a deploy that registered an abuse-detection listener without shipping the companion fake-chat models. End users see it only if their prompt tripped moderation on such a misconfigured server.","solutions":["Register a 'fake' (and optionally 'abuse') model in the fake-chat provider config so #resolveModel succeeds.","If moderation is not wanted, disable or remove the listener that sets event.allow=false on ai.prompt.validate.","Verify the model registry entry the driver resolves with the 'fake-chat' intended provider name.","As an end user, rephrase the prompt to avoid the moderation trigger, or contact the instance operator about the missing fake-chat model."],"exampleFix":"// config.json models array — add a fake-chat entry\n// before: fake-chat provider has no models registered\n// after:\n{\n  \"provider\": \"fake-chat\",\n  \"id\": \"fake\",\n  \"intended_provider\": \"fake-chat\"\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const res = await driver.complete(args);\n} catch (e) {\n  if (e instanceof HttpError && e.status === 403 && e.body?.legacyCode === 'forbidden') {\n    // prompt tripped moderation and the instance has no fake-chat model;\n    // surface a user-facing 'content not allowed' message rather than retry\n    return showModerationNotice();\n  }\n  throw e;\n}","preventionTips":["Operators: always register 'fake' (and 'abuse' if moderation phones home) models when a prompt.validate listener is enabled.","Do not retry the same prompt — the moderation result is deterministic for the same input.","Surface the error as a content-policy notice to the end user; it is not a transient failure."],"tags":["ai-chat","moderation","config","forbidden"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}