paperclipai/paperclip · error · Error
device-login credential promotion rejected: the existing ${s
Error message
device-login credential promotion rejected: the existing ${secretName} secret does not name this account's own home What it means
assertAccountHomeSecretMatches re-resolves the value of a pre-existing `CODEX_HOME_<handle>` company secret during device-login credential promotion and throws if that stored value is not exactly this account's expected home directory. The secret's name alone is not proof it points at this account's home: a stale path (e.g. after the cache root moved) or a hand-edited value would otherwise let login report success while bound agents read the wrong or missing CODEX_HOME. The check deliberately fails loud under withAccountHomeSecretMutationLock.
Source
Thrown at server/src/routes/agents.ts:319
// A caller that only checks once, early in the promotion, and then reports
// success later is not enough on its own: the lock this call held is fully
// released by the time it returns, so a rotate queued behind it can commit a
// new value before the login service records its terminal `authenticated`
// state, which happens well after this call returns (see `runTerminalCommit`
// below, which runs this same check again, under a fresh lock acquisition,
// immediately before that terminal state commits).
async function assertAccountHomeSecretMatches(
secretsSvc: { resolveSecretValueForDeviceLoginCheck: (companyId: string, secretId: string, context: { configPath: string }) => Promise<string> },
companyId: string,
secret: { id: string },
secretName: string,
expectedAccountHomeDir: string,
): Promise<void> {
const storedValue = await secretsSvc.resolveSecretValueForDeviceLoginCheck(companyId, secret.id, {
configPath: `secrets.${secretName}`,
});
if (storedValue !== expectedAccountHomeDir) {
throw new Error(
`device-login credential promotion rejected: the existing ${secretName} secret does not name this account's own home`,
);
}
}
// Confirms no company secret, under any name or provider, still names this
// account home before a failed promotion deletes the directory. The
// generated `CODEX_HOME_<handle>` name is not the only secret that can
// reference this directory: a user can bind a hand-named secret to the same
// account home, so a check that reads only the generated name misses that
// secret and deletes a directory it still needs. A bound agent then reads a
// `CODEX_HOME` value that points at nothing. A secret's value is a plain
// string regardless of its provider, so an AWS Secrets Manager-backed secret
// (or any other provider) can equal this directory's path just as a
// `local_encrypted` secret can; the scan resolves every secret's value, not
// only `local_encrypted` ones.
//
// A secret whose value fails to resolve is NOT proof that secret names aView on GitHub (pinned to 01ad858492)
Solutions
- Inspect the secret's current value (secrets.<CODEX_HOME_<handle>>) and update it to the account's actual current home directory, then retry the login.
- If the home path moved intentionally, re-point the secret at the new directory (or delete the secret so login re-creates it with the correct value).
- If a concurrent rotate caused the mismatch, wait for the in-flight secret rotation to settle and retry the device login.
- Do not bypass the lock; ensure any tooling that edits this secret holds withAccountHomeSecretMutationLock.
Example fix
// before (hand-edited secret breaks login) // secrets.CODEX_HOME_user_ab12 = "/old/cache/codex" (home is now /new/cache/codex) // after // secrets.CODEX_HOME_user_ab12 = "/new/cache/codex" // matches expectedAccountHomeDir
Defensive patterns
Strategy: validation
Validate before calling
// Before logging in, verify the existing secret still names the account home
const secret = await secretsSvc.getByName(companyId, `CODEX_HOME_${handle}`);
if (secret) {
const value = await secretsSvc.resolve(companyId, secret.id);
if (value !== expectedAccountHomeDir) {
// re-point or delete the secret before login
await secretsSvc.update(companyId, secret.id, { value: expectedAccountHomeDir });
}
} Try / catch
try {
await loginWithDeviceFlow(...);
} catch (e) {
if (e instanceof Error && e.message.includes('does not name this account\'s own home')) {
console.error('CODEX_HOME secret value diverged from the account home; fix or delete the secret and retry');
} else throw e;
} Prevention
- Never hand-edit CODEX_HOME_<handle> secrets; let the login flow own them
- When moving the Codex cache root, update or delete the account-home secrets in the same change
- Serialize any secret rotations on CODEX_HOME_* through withAccountHomeSecretMutationLock
- After restoring DB backups, reconcile secret values against on-disk home directories
When it happens
Trigger: A device login for a Codex account whose `CODEX_HOME_<handle>` secret already exists, and resolveSecretValueForDeviceLoginCheck returns a value !== expectedAccountHomeDir — e.g. the user hand-edited the secret, the value predates a cache-root move, or a concurrent local_encrypted secret rotate changed the value between create and this re-check.
Common situations: Operator manually overwrote the CODEX_HOME secret with a different path; dev machine moved the Codex cache directory; two logins racing with a secret rotation queued behind the account-home lock; restoring a DB backup where the secret value no longer matches the on-disk home.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- codex auth cache: account-home directory no longer exists; r
- device-login credential promotion rejected: the promotion ca
- device-login credential promotion rejected: the ${secretName
- device-login credential promotion rejected: failed to record
- ${name} must be a JSON object
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/5859acf2f8d1d401.
Report an issue: GitHub.