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 a

View on GitHub (pinned to 01ad858492)

Solutions

  1. 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.
  2. 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).
  3. If a concurrent rotate caused the mismatch, wait for the in-flight secret rotation to settle and retry the device login.
  4. 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

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/5859acf2f8d1d401. Report an issue: GitHub.