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

  1. 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).
  2. Retry the device login once concurrent activity settles; races on the same account secret name are the most common transient trigger.
  3. Verify no orphaned/hand-created secret names the same account home directory; resolve or remove conflicting secrets for the company.
  4. If the login created the account home dir and it was rolled back, remove stale directories and re-run the login cleanly.
  5. 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

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


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