{"record":{"id":"5859acf2f8d1d401","repo":"paperclipai/paperclip","slug":"device-login-credential-promotion-rejected-the-ex","errorCode":null,"errorMessage":"device-login credential promotion rejected: the existing ${secretName} secret does not name this account's own home","messagePattern":"device-login credential promotion rejected: the existing (.+?) secret does not name this account's own home","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/routes/agents.ts","lineNumber":319,"sourceCode":"// A caller that only checks once, early in the promotion, and then reports\n// success later is not enough on its own: the lock this call held is fully\n// released by the time it returns, so a rotate queued behind it can commit a\n// new value before the login service records its terminal `authenticated`\n// state, which happens well after this call returns (see `runTerminalCommit`\n// below, which runs this same check again, under a fresh lock acquisition,\n// immediately before that terminal state commits).\nasync function assertAccountHomeSecretMatches(\n  secretsSvc: { resolveSecretValueForDeviceLoginCheck: (companyId: string, secretId: string, context: { configPath: string }) => Promise<string> },\n  companyId: string,\n  secret: { id: string },\n  secretName: string,\n  expectedAccountHomeDir: string,\n): Promise<void> {\n  const storedValue = await secretsSvc.resolveSecretValueForDeviceLoginCheck(companyId, secret.id, {\n    configPath: `secrets.${secretName}`,\n  });\n  if (storedValue !== expectedAccountHomeDir) {\n    throw new Error(\n      `device-login credential promotion rejected: the existing ${secretName} secret does not name this account's own home`,\n    );\n  }\n}\n\n// Confirms no company secret, under any name or provider, still names this\n// account home before a failed promotion deletes the directory. The\n// generated `CODEX_HOME_<handle>` name is not the only secret that can\n// reference this directory: a user can bind a hand-named secret to the same\n// account home, so a check that reads only the generated name misses that\n// secret and deletes a directory it still needs. A bound agent then reads a\n// `CODEX_HOME` value that points at nothing. A secret's value is a plain\n// string regardless of its provider, so an AWS Secrets Manager-backed secret\n// (or any other provider) can equal this directory's path just as a\n// `local_encrypted` secret can; the scan resolves every secret's value, not\n// only `local_encrypted` ones.\n//\n// A secret whose value fails to resolve is NOT proof that secret names a","sourceCodeStart":301,"sourceCodeEnd":337,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/routes/agents.ts#L301-L337","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (hand-edited secret breaks login)\n// secrets.CODEX_HOME_user_ab12 = \"/old/cache/codex\"  (home is now /new/cache/codex)\n// after\n// secrets.CODEX_HOME_user_ab12 = \"/new/cache/codex\"  // matches expectedAccountHomeDir","handlingStrategy":"validation","validationCode":"// Before logging in, verify the existing secret still names the account home\nconst secret = await secretsSvc.getByName(companyId, `CODEX_HOME_${handle}`);\nif (secret) {\n  const value = await secretsSvc.resolve(companyId, secret.id);\n  if (value !== expectedAccountHomeDir) {\n    // re-point or delete the secret before login\n    await secretsSvc.update(companyId, secret.id, { value: expectedAccountHomeDir });\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await loginWithDeviceFlow(...);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('does not name this account\\'s own home')) {\n    console.error('CODEX_HOME secret value diverged from the account home; fix or delete the secret and retry');\n  } else throw e;\n}","preventionTips":["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"],"tags":["secrets","device-login","codex","state-mismatch"],"backgroundTag":"invalid-argument-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}