paperclipai/paperclip · error
Provider credential is not ready
Error message
Provider credential is not ready
What it means
This server throws when an agent-adapter login promotion attempts to save staged provider credentials but the staged auth bytes have not passed the provider-specific readiness check (checkStagedCredentialReadiness). Promotion binds staged credentials into an AI connection, and the code refuses to persist credentials that are not yet verifiably usable, while holding the per-company adapter promotion lock to keep the critical section safe. It protects the aiConnection from being overwritten with incomplete or unusable auth material.
Solutions
- Wait for the staged credential readiness check to pass, then retry promotion.
- Re-run the adapter login flow to re-stage fresh credentials, then promote again.
- Check whether a concurrent login for the same adapter/company failed and removed the staged secret directory; serialize logins rather than running them in parallel.
- Inspect server logs around checkStagedCredentialReadiness to see why the staged bytes were not ready.
Example fix
// before
await adapterLoginStore.promote(authBytes, context); // throws if not ready
// after
const staged = await adapterLoginStore.get(context.sessionId);
if (!staged) throw new Error("Login session expired; start the login again");
try {
await adapterLoginStore.promote(authBytes, context);
} catch (e) {
if (e.message === "Provider credential is not ready") {
// guide the user to re-run the login flow
return res.status(409).json({ error: "Credential not ready; redo the provider login" });
}
throw e;
} Defensive patterns
Strategy: validation
Validate before calling
const staged = await adapterLoginStore.get(sessionId);
if (!staged) throw new Error("Session expired; redo login");
// promote only after login flow reports credentials staged/validated Type guard
function isPromotable(s) { return Boolean(s && s.aiConnection && s.stagedAt && !s.stagingError); } Try / catch
try { await adapterLoginStore.promote(authBytes, ctx); }
catch (e) {
if (e.message === "Provider credential is not ready") return redoLoginFlow();
throw e;
} Prevention
- Only call promote after the login flow signals completion
- Never run two logins for the same adapter/company in parallel
- Re-run login rather than retrying promotion when this error appears
- Monitor staged-session age; stale sessions often fail readiness
When it happens
Trigger: Calling the agent login promote endpoint while the staged auth bytes for the adapter have not completed provider validation — e.g. the first-login credential check has not finished, the staged secret directory was removed by a concurrent login's secret-write failure, or the staged payload was corrupted or truncated.
Common situations: Two logins for the same adapter racing where the first's later secret-write failure removes the shared staged directory; a user clicking 'finish login' before the provider handshake completes; stale staged sessions being promoted after a server restart or provider-side credential rotation.
Related errors
- ACPX runtime executable changed while it was verified
- ACPX executable changed during snapshot
- ACPX file ended during snapshot
- ACPX module changed before snapshot
- ACPX module changed during snapshot
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/e5351bb75a4ea00f.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/routes/agents.ts:803
promotionByAdapterType: {
codex_local: {
// Hold one lock across the whole promotion sequence below: the
// credential write, the existing-secret check, the secret create, and
// the cleanup a create failure can trigger. Two different logins for
// the SAME Codex account run this whole sequence one at a time, so a
// login can never decide to delete the shared account-home directory
// while another login's own sequence is still mid-way through writing
// its credential or binding its own secret to that same directory. A
// lock around only the directory-creation step is not enough: that
// lock is already released by the time a login reaches the secret
// bind, so a second login can write its credential and be about to
// bind its own secret while the first login's later, unrelated
// secret-write failure removes the directory both logins now share.
async promote(authBytes, context) {
const managedSession = await adapterLoginStore.get(context.sessionId);
if (managedSession?.aiConnection) {
await adapterLoginStore.withCompanyAdapterPromotionLock(context.companyId, context.startedByUserId, context.adapterType, async () => {
if (!(await checkStagedCredentialReadiness(authBytes)).ready) throw new Error("Provider credential is not ready");
await aiConnectionService(db).save(context.companyId, context.startedByUserId, managedSession.aiConnection!, authBytes.toString("utf8"), context.sessionId);
});
return;
}
return withCodexAccountHomePromotionLock(undefined, context.companyId, async () => {
// Hold the promotion critical-section lock across the ownership check
// and the credential write. The reaper takes the same lock before it
// reclaims a stale `promoting` row. So a reclaim never interleaves with
// a live write: the reaper either wins the lock first and the
// ownership check then reads a reclaimed row and writes nothing, or
// the write finishes first under the lock and the reaper reclaims only
// after it completes. A read-only fence is not enough, because the
// filesystem write can start after the fence; the lock spans the whole
// section.
const result = await adapterLoginStore.withCompanyAdapterPromotionLock(
context.companyId,
context.startedByUserId,View on GitHub (pinned to 3f1d897a7c)