{"record":{"id":"43d701c22c385719","repo":"amruthpillai/reactive-resume","slug":"precondition-failed","errorCode":"PRECONDITION_FAILED","errorMessage":"AI agent workspace is unavailable because REDIS_URL or ENCRYPTION_SECRET is not configured.","messagePattern":"AI agent workspace is unavailable because REDIS_URL or ENCRYPTION_SECRET is not configured\\.","errorType":"http","errorClass":"ORPCError","httpStatus":412,"severity":"error","filePath":"packages/api/src/features/agent/routing.ts","lineNumber":10,"sourceCode":"import type { AnyMiddleware } from \"@orpc/server\";\nimport type { UIMessage } from \"ai\";\nimport { ORPCError } from \"@orpc/client\";\n\nfunction isAgentEnvironmentUnavailable(error: unknown) {\n\treturn error instanceof Error && error.message === \"AGENT_ENVIRONMENT_UNAVAILABLE\";\n}\n\nfunction throwUnavailable(): never {\n\tthrow new ORPCError(\"PRECONDITION_FAILED\", {\n\t\tmessage: \"AI agent workspace is unavailable because REDIS_URL or ENCRYPTION_SECRET is not configured.\",\n\t});\n}\n\nexport function isUiMessage(value: unknown): value is UIMessage {\n\tif (!value || typeof value !== \"object\") return false;\n\n\tconst message = value as Partial<UIMessage>;\n\treturn (\n\t\ttypeof message.id === \"string\" &&\n\t\t(message.role === \"system\" || message.role === \"user\" || message.role === \"assistant\") &&\n\t\tArray.isArray(message.parts)\n\t);\n}\n\n// ponytail: single middleware replaces 12 near-identical try/catch blocks across agent route handlers\nexport const mapAgentEnvironmentError: AnyMiddleware = async ({ next }) => {\n\ttry {","sourceCodeStart":1,"sourceCodeEnd":28,"githubUrl":"https://github.com/amruthpillai/reactive-resume/blob/3a5b12e2a40374a9571988701fcb75c5a1831c42/packages/api/src/features/agent/routing.ts#L1-L28","documentation":"A PRECONDITION_FAILED ORPCError raised by the mapAgentEnvironmentError middleware whenever the underlying service call throws an Error whose message is exactly 'AGENT_ENVIRONMENT_UNAVAILABLE'. That inner error comes from assertAgentEnvironment (packages/api/src/features/ai/credentials.ts), which requires both env.ENCRYPTION_SECRET (for AES-256-GCM credential encryption) and env.REDIS_URL (for resumable agent streaming) to be set. The message names both because either one missing disables the whole agent workspace.","triggerScenarios":"Calling any agent route (threads.create, threads.getOrCreateForResume, messages.send, attachments.*) in a server process where REDIS_URL is unset or whitespace-only OR ENCRYPTION_SECRET is unset/whitespace; running the dev server without these in .env; deploying with Turborepo strict env mode without listing the vars in turbo.json globalEnv (so they get filtered out of child processes).","commonSituations":"Fresh checkout that only set APP_URL/DATABASE_URL/AUTH_SECRET (the three required vars) but never added the optional agent vars; ENCRYPTION_SECRET was set once then removed when re-deploying; REDIS_URL points at a Redis that is not started; Turborepo 2.x strict env dropped the var because it isn't declared in turbo.json.","solutions":["Add ENCRYPTION_SECRET (any non-empty secret, ideally 32+ random bytes) and REDIS_URL (e.g. redis://localhost:6379) to .env / .env.local and restart the server.","If using Turborepo, confirm both names appear in turbo.json globalEnv or the relevant task env, otherwise they will be undefined inside the spawned process even when set in the OS.","Start the Redis service (docker compose up -d redis or equivalent) and confirm connectivity before retrying.","Guard the UI: hide/disable agent features until GET /api/rpc/agent.health (or equivalent capability flag) reports available, so users never reach this precondition."],"exampleFix":"# before\nAPP_URL=http://localhost:3000\nDATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres\nAUTH_SECRET=dev-secret\n\n# after (add the two agent env vars)\nAPP_URL=http://localhost:3000\nDATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres\nAUTH_SECRET=dev-secret\nENCRYPTION_SECRET=__generate_32_random_bytes__\nREDIS_URL=redis://localhost:6379","handlingStrategy":"validation","validationCode":"// Call a capability/health probe before exposing agent UI.\nconst capable = await orpc.agent.capabilities(); // or whatever the capability route is\nif (!capable.agentEnvironment) hideAgentFeatures();","typeGuard":"function isAgentEnvConfigured(env: { REDIS_URL?: string; ENCRYPTION_SECRET?: string }): boolean {\n  return !!env.REDIS_URL?.trim() && !!env.ENCRYPTION_SECRET?.trim();\n}","tryCatchPattern":null,"preventionTips":["Add REDIS_URL and ENCRYPTION_SECRET to the deployment env template (and to turbo.json globalEnv).","Run a startup check that fails fast if the agent env is required but not configured.","Keep the agent capability flag in the session so the UI can hide features before any call is attempted.","Generate ENCRYPTION_SECRET once and treat it as a long-term secret; rotating it invalidates all stored provider keys."],"tags":["agent","environment","redis","encryption","configuration","orpc","precondition"],"backgroundTag":null,"analyzedSha":"3a5b12e2a40374a9571988701fcb75c5a1831c42","analyzedAt":"2026-08-12T22:31:22.666Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}