can1357/oh-my-pi · error · AIError.OAuthError

Z.ai key provisioning returned no secretKey

Error message

Z.ai key provisioning returned no secretKey

What it means

Listed key entries mask the secret, so mintZaiApiKey always fetches the plaintext via the copy endpoint (/copy/{apiKey}) and requires a non-empty secretKey in the response. If absent, OAuthError is thrown — the key identifier exists but its secret could not be retrieved, leaving an unusable half-credential.

Source

Thrown at packages/ai/src/registry/oauth/zai.ts:207

	const apiKey = trimmedString(keyRecord?.apiKey);
	if (!apiKey) {
		throw new AIError.OAuthError("Z.ai key provisioning returned no apiKey", {
			kind: "token-exchange",
			provider: "zai",
		});
	}

	// Always fetch the secret via the copy endpoint: list entries mask it
	// (`*****abcd`) and the create response's inline secret is not reliable
	// across account states, whereas copy always returns the full secret.
	const copied = unwrapEnvelope(
		await getJson(`${keysUrl}/copy/${encodeURIComponent(apiKey)}`, auth, fetchImpl),
		"api key copy",
	) as { secretKey?: unknown } | undefined;
	const secretKey = trimmedString(copied?.secretKey);
	if (!secretKey) {
		throw new AIError.OAuthError("Z.ai key provisioning returned no secretKey", {
			kind: "token-exchange",
			provider: "zai",
		});
	}

	return `${apiKey}.${secretKey}`;
}

export class ZaiOAuthFlow extends OAuthCallbackFlow {
	#fetch: FetchImpl;

	constructor(ctrl: OAuthController) {
		super(ctrl, {
			preferredPort: CALLBACK_PORT,
			callbackPath: CALLBACK_PATH,
			allowPortFallback: false,
		});
		this.#fetch = ctrl.fetch ?? fetch;

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the same regional BIZ_BASE and auth are used for create and copy; re-run the full mint flow.
  2. Check the account/token has permission to copy (reveal) key secrets — some roles can create but not reveal.
  3. Log the copy response to detect renamed fields and update the library if the schema changed.
  4. On failure, delete the orphaned apiKey via the API to avoid accumulating unusable keys.

Example fix

// before
const copied = await getJson(`${keysUrl}/copy/${encodeURIComponent(apiKey)}`, auth, fetch);
// after
let copied = await getJson(`${keysUrl}/copy/${encodeURIComponent(apiKey)}`, auth, fetch).catch(async e => {
  await deleteZaiKey(keysUrl, apiKey, auth, fetch); // clean up unusable key
  throw e;
});
Defensive patterns

Strategy: try-catch

Validate before calling

if (!apiKey) throw new Error("Cannot fetch secret: apiKey missing from create response");

Type guard

function hasSecretKey(r: unknown): r is { secretKey: string } { return !!r && typeof r === "object" && typeof (r as Record<string, unknown>).secretKey === "string" && (r as { secretKey: string }).secretKey.length > 0; }

Try / catch

try { return await mintZaiApiKey(token, fetch); }
catch (e) {
  if (e instanceof AIError.OAuthError && e.message.includes("no secretKey")) {
    await deleteZaiKeySafely(token, apiKey).catch(() => {}); // avoid orphaned key
    return retryMintOnce();
  }
  throw e;
}

Prevention

When it happens

Trigger: Copy endpoint returns envelope without secretKey: insufficient permission to copy, key created in a different region than the copy request, Z.ai changed the field name, or the copy endpoint rejects the URL-encoded key.

Common situations: Region mismatch between key creation and copy call; token lacking copy scope; Z.ai API schema drift; key deleted between create and copy.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3a0350a43b867a93. Report an issue: GitHub.