amruthpillai/reactive-resume · critical · ORPCError

PRECONDITION_FAILED

PRECONDITION_FAILED

Error message

AI providers are unavailable because ENCRYPTION_SECRET is not configured.

What it means

PRECONDITION_FAILED thrown by throwCredentialEncryptionUnavailable. Reached when an AI feature's catch block detects the sentinel error message 'AI_CREDENTIAL_ENCRYPTION_UNAVAILABLE' (isCredentialEncryptionUnavailable), which assertCredentialEncryptionConfigured raises when the server lacks the ENCRYPTION_SECRET needed to decrypt stored API keys. AI features cannot run because credentials cannot be read.

Source

Thrown at packages/api/src/features/ai/router.ts:36

function isAiProviderGatewayError(error: unknown): boolean {
	return error instanceof AISDKError;
}

function isCredentialEncryptionUnavailable(error: unknown): boolean {
	return error instanceof Error && error.message === "AI_CREDENTIAL_ENCRYPTION_UNAVAILABLE";
}

/** Throws a BAD_GATEWAY ORPCError, preserving the original cause for upstream error reporters. */
function throwAiProviderGatewayError(cause?: unknown): never {
	throw new ORPCError("BAD_GATEWAY", { message: "Could not reach the AI provider.", cause });
}

function throwAiProviderConfigError(): never {
	throw new ORPCError("BAD_REQUEST", { message: "Invalid AI provider configuration." });
}

function throwCredentialEncryptionUnavailable(): never {
	throw new ORPCError("PRECONDITION_FAILED", {
		message: "AI providers are unavailable because ENCRYPTION_SECRET is not configured.",
	});
}

function throwResumeStructureError(error: ZodError): never {
	throw new ORPCError("BAD_REQUEST", {
		message: "Invalid resume data structure",
		cause: flattenError(error),
	});
}

async function getRunnableProvider(userId: string, aiProviderId?: string) {
	const provider = aiProviderId
		? await aiProvidersService.getRunnableById({ id: aiProviderId, userId })
		: await aiProvidersService.getDefaultRunnable({ userId });

	if (!provider) throw new ORPCError("BAD_REQUEST", { message: "No tested AI provider is available." });

View on GitHub (pinned to 3a5b12e2a4)

Solutions

  1. Set ENCRYPTION_SECRET in the server environment to a stable, sufficiently random value and restart.
  2. Keep ENCRYPTION_SECRET constant across restarts — changing it invalidates previously encrypted API keys.
  3. Add a startup check / health probe that fails fast when ENCRYPTION_SECRET is absent.
  4. After setting it, re-enter API keys for existing providers (if the secret changed they will be undecryptable).

Example fix

# before
# .env has no ENCRYPTION_SECRET

# after
ENCRYPTION_SECRET=$(openssl rand -hex 32)
Defensive patterns

Strategy: validation

Validate before calling

function assertEncryptionSecretConfigured() {
  if (!process.env.ENCRYPTION_SECRET) throw new Error('ENCRYPTION_SECRET missing');
}

Type guard

function isEncryptionUnavailable(e) {
  return e?.code === 'PRECONDITION_FAILED' && /ENCRYPTION_SECRET/i.test(e.message);
}

Try / catch

try {
  await ai.parsePdf(input);
} catch (e) {
  if (e?.code === 'PRECONDITION_FAILED' && /ENCRYPTION_SECRET/i.test(e.message)) {
    showAdminNotice('Set ENCRYPTION_SECRET on the server to enable AI.');
  } else throw e;
}

Prevention

When it happens

Trigger: Any AI feature invocation on a deployment where ENCRYPTION_SECRET is missing or empty, so decryptCredential/encryptCredential refuse to operate and the service throws the sentinel before reading any provider.

Common situations: Fresh deployment that did not set ENCRYPTION_SECRET; secret rotation that left the env var unset; container restart after a config change dropped the secret; the provider list itself returns a PRECONDITION_FAILED from the same root cause.

Related errors


AI-assisted analysis of amruthpillai/reactive-resume@3a5b12e2a4 (2026-08-12). Data as JSON: /api/errors/ae21b68461e6fbfb. Report an issue: GitHub.