decolua/9router · error · Error
Missing Zed private key verifier; restart the login flow
Error message
Missing Zed private key verifier; restart the login flow
What it means
Zed native-app login stores an RSA private key wrapped in a special prefixed verifier string (PRIVATE_KEY_PREFIX + base64url). decodeZedPrivateKeyVerifier parses that verifier back into the PEM key material and throws when the input does not carry the expected prefix — meaning the value is not a valid private-key verifier from the current login flow.
Source
Thrown at open-sse/shared/zedAuth.js:67
function normalizeBaseUrl(baseUrl, fallback) {
return String(baseUrl || fallback).replace(/\/+$/, "");
}
function zedUrl(config, key, path, fallbackBase) {
const base = normalizeBaseUrl(config?.[key], fallbackBase);
return `${base}${path}`;
}
/** Encode a PEM private key as an opaque verifier (flows through the OAuth codeVerifier slot). */
export function encodeZedPrivateKeyVerifier(privateKeyPem) {
return `${PRIVATE_KEY_PREFIX}${b64url(privateKeyPem)}`;
}
export function decodeZedPrivateKeyVerifier(verifier) {
const value = String(verifier || "");
if (!value.startsWith(PRIVATE_KEY_PREFIX)) {
throw new Error("Missing Zed private key verifier; restart the login flow");
}
return fromB64url(value.slice(PRIVATE_KEY_PREFIX.length));
}
/** Generate a fresh RSA keypair + the zed.dev native_app_signin URL for it. */
export function createZedNativeAuthData(config = {}, options = {}) {
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "pkcs1", format: "der" },
privateKeyEncoding: { type: "pkcs1", format: "pem" },
});
const nativeAppPort = Number(
options.nativeAppPort || config.defaultNativeAppPort || 58443,
);
const systemId = options.systemId || crypto.randomUUID();
const publicKeyString = b64urlPadded(publicKey);
const signInUrl = new URL(View on GitHub (pinned to 90b52e06ff)
Solutions
- Restart the Zed login flow (createZedNativeAuthData) to generate a fresh keypair + verifier
- Verify the stored verifier is the full prefixed string, not a truncated copy or the callback URL
- Check for version mismatch — old verifiers from a previous prefix format are unrecoverable, re-auth
- Ensure storage did not strip the prefix (e.g. URL parsing of the stored value)
Example fix
// before const pem = decodeZedPrivateKeyVerifier(account.callbackUrl); // wrong field // after const pem = decodeZedPrivateKeyVerifier(account.privateKeyVerifier); if (!account.privateKeyVerifier?.startsWith(PRIVATE_KEY_PREFIX)) await restartZedLogin(account);
Defensive patterns
Strategy: validation
Validate before calling
function hasValidZedVerifier(v) {
return typeof v === "string" && v.startsWith(PRIVATE_KEY_PREFIX) && v.length > PRIVATE_KEY_PREFIX.length;
}
if (!hasValidZedVerifier(account.zedVerifier)) await startZedLogin(account); Type guard
function isZedPrivateKeyVerifier(v) { return typeof v === "string" && v.startsWith(PRIVATE_KEY_PREFIX); } Try / catch
try {
const pem = decodeZedPrivateKeyVerifier(verifier);
} catch (e) {
if (/Missing Zed private key verifier/.test(e.message)) {
const fresh = await createZedNativeAuthData(config);
// restart login: present fresh.verificationUriComplete to the user
} else throw e;
} Prevention
- Store the verifier verbatim — never trim or URL-decode it
- Persist the verifier with the keypair, not in memory only
- Treat this error as 'login state lost' → always restart the flow
- Guard against version-format changes by checking the prefix before decode
When it happens
Trigger: privateKey() is called with a verifier string that is empty, truncated, JSON-escaped, or was generated by an older build using a different prefix format — i.e. the string does not start with PRIVATE_KEY_PREFIX.
Common situations: User pasted only the callback URL instead of the stored verifier; app restarted mid-login so the in-memory verifier was lost; schema/format change between versions; verifier stored in DB was corrupted or trimmed.
Related errors
- Missing Zed callback URL
- No Zed organization selected
- Zed did not return an LLM token
- Kiro tool input must be a JSON object
- Vertex OAuth/ADC requires a project_id. Add quota_project_id
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/136a5a710f1cb925.
Report an issue: GitHub.