paperclipai/paperclip · error · Error
device-login credential promotion rejected: failed to record
Error message
device-login credential promotion rejected: failed to record the account home secret
What it means
This error is thrown inside the device-login credential promotion callback in the agents route. During a device login, the login service calls `promote` to persist the account's home directory as a company secret (`local_encrypted`). If recording that secret fails for any reason other than a resolvable 409 naming conflict, the code cleans up any freshly-created account home directory (only when no other secret claims it) and throws this error so the login is rejected rather than committed without a recorded credential.
Source
Thrown at server/src/routes/agents.ts:940
// either, because the claiming secret can carry any name.
//
// The check and the delete run inside `withAccountHomeSecretMutationLock`,
// the same lock the secrets service holds for the whole of a
// `local_encrypted` secret's create or rotate call. That closes the
// window `anySecretNamesAccountHome`'s own multi-pass scan cannot: a
// secret write that commits after this check's last pass but before
// the delete runs. Under the shared lock, a write either finishes
// (and becomes visible to the check) before this section acquires the
// lock, or it waits for this section to finish before it can commit.
if (result.accountHomeCreated) {
await withAccountHomeSecretMutationLock(undefined, context.companyId, async () => {
const claimed = await anySecretNamesAccountHome(secretsSvc, context.companyId, accountHomeDir);
if (!claimed) {
await rm(accountHomeDir, { recursive: true, force: true }).catch(() => undefined);
}
});
}
throw new Error(
"device-login credential promotion rejected: failed to record the account home secret",
);
}
});
},
// The login service calls this immediately before it commits its
// terminal `authenticated` write, wrapping that write in the
// callback it hands in as `commit`. `promote` above already
// validated the bound account-home secret once, early, but its own
// lock is fully released by the time `promote` returns — well before
// this runs. Re-run the same check here, and hold the SAME lock
// across both the check and `commit`, so a rotate cannot land in the
// gap between the validated value and the terminal write that
// reports it as authenticated: a rotate either finishes (and this
// check reads its new value, and rejects) before this section
// acquires the lock, or it waits for this section — including the
// terminal commit — to finish first.
async runTerminalCommit(commit, context) {View on GitHub (pinned to 01ad858492)
Solutions
- Check the secrets service and database health at the time of login (the underlying cause is logged by the secret write that failed before this throw).
- Retry the device login once concurrent activity settles; races on the same account secret name are the most common transient trigger.
- Verify no orphaned/hand-created secret names the same account home directory; resolve or remove conflicting secrets for the company.
- If the login created the account home dir and it was rolled back, remove stale directories and re-run the login cleanly.
- Inspect the underlying error thrown by secretsSvc.create (logged before this throw) for encryption/backend-specific failures and fix that root cause.
Example fix
// before: opaque error propagates from login service
throw new Error("device-login credential promotion rejected: failed to record the account home secret");
// after: preserve the underlying cause for debugging
throw new Error(
"device-login credential promotion rejected: failed to record the account home secret",
{ cause: err },
); Defensive patterns
Strategy: try-catch
Validate before calling
// before initiating device login
const healthy = await fetch('/api/health');
if (!healthy.ok) throw new Error('server/secrets backend not healthy; defer device login');
// ensure no conflicting secret exists for this account
const existing = await secretsSvc.getByName(companyId, secretName);
if (existing) await verifySecretMatchesAccountHome(existing, accountHomeDir); Type guard
function isPromotionRejection(err: unknown): err is Error & { message: string } {
return err instanceof Error && err.message.startsWith('device-login credential promotion rejected:');
} Try / catch
try {
await startDeviceLogin(adapter, accountHomeDir);
} catch (err) {
if (isPromotionRejection(err)) {
// inspect cause, clean stale account home dir, retry once after settling
await retryWithBackoff(() => startDeviceLogin(adapter, accountHomeDir), { attempts: 2 });
} else throw err;
} Prevention
- Check DB and secrets service health before starting device logins.
- Avoid running concurrent logins for the same account in the same company.
- Do not hand-create secrets that point into adapter-managed account home directories.
- Always carry `{ cause: err }` so the underlying secret-write failure is inspectable.
When it happens
Trigger: A device-login flow calls the adapter login service which invokes the `promote` callback; the secret write to the secrets service fails with a non-409 error (secret service unavailable, encryption failure, DB write error, malformed secret payload), or a 409 conflict occurs but the winning secret cannot be found/verified against the account home directory.
Common situations: Database down or migrated mid-login; concurrent logins racing on the same account secret name; a manually created secret pointing at the same account home directory with a different name; disk/encryption key misconfiguration in the local_encrypted secret backend; secrets service version drift after an upgrade.
Related errors
- device-login credential promotion rejected: the existing ${s
- device-login credential promotion rejected: the ${secretName
- ${name} must be a JSON object
- Invalid ${name} JSON: ${err instanceof Error ? err.message :
- --file is required
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/a02aba2bb34a7ffa.
Report an issue: GitHub.